场景介绍
public class Lock{
protected MonitorObject monitorObject = new MonitorObject();
protected boolean isLocked = false;
public void lock() throws InterruptedException{
synchronized(this){
while(isLocked){
synchronized(this.monitorObject){
this.monitorObject.wait();
}
}
isLocked = true;
}
}
public void unlock(){
synchronized(this){
this.isLocked = false;
synchronized(this.monitorObject){
this.monitorObject.notify();
}
}
}
}
- lock()方法在this上同步,然后在monitorObject上同步。如果isLocked为true,调用lock()方法的线程会在wait()方法上阻塞。
- 如果此时需要unlock,由于wait()只释放了monitorObject的锁,this相关的管程对象并没有释放,所以unlock()方法等待lock()方法释放锁,而lock()方法必须等待unlock()方法的isLocked变为false。
简而言之,在lock方法中等待的线程需要其它线程成功调用unlock方法来退出lock方法,但是,在lock()方法离开外层同步块之前,没有线程能成功执行unlock()。
另一个例子
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads =
new ArrayList<QueueObject>();
public void lock() throws InterruptedException{
QueueObject queueObject = new QueueObject();
synchronized(this){
waitingThreads.add(queueObject);
while(isLocked || waitingThreads.get(0) != queueObject){
synchronized(queueObject){
try{
queueObject.wait();
}catch(InterruptedException e){
waitingThreads.remove(queueObject);
throw e;
}
}
}
waitingThreads.remove(queueObject);
isLocked = true;
lockingThread = Thread.currentThread();
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if(waitingThreads.size() > 0){
QueueObject queueObject = waitingThread.get(0);
synchronized(queueObject){
queueObject.notify();
}
}
}
}
- 方法内部有两个synchronized块,一个锁定this,一个嵌在上一个synchronized块内部,它锁定的是局部变量queueObject。
- 当一个线程调用queueObject.wait()方法的时候,它仅仅释放的是在queueObject对象实例的锁,并没有释放”this”上面的锁。
- unlock方法被声明成了synchronized,这就相当于一个synchronized(this)块。这就意味着,如果一个线程在lock()中等待,该线程将持有与this关联的管程对象。所有调用unlock()的线程将会一直保持阻塞,等待着前面那个已经获得this锁的线程释放this锁,但这永远也发生不了,因为只有某个线程成功地给lock()中等待的线程发送了信号,this上的锁才会释放,但只有执行unlock()方法才会发送这个信号。
嵌套管程锁死VS死锁
管程:获取锁的顺序是一致的。线程1获取A和B,然后释放B,等待2的信号。线程2需要同时获得A和B,才能向1发送信号。一个线程等待唤醒,另一个线程等待想要的锁被释放。
死锁:两个线程获取锁的顺序不一致,两个线程都在等待对方释放锁。
Slipped Condition
slipped condition是说从一个线程检查一个特定条件到该线程操作此条件期间,这个条件已经被其他线程改变,导致第一个线程在该条件上发生了错误的操作。
public class Lock {
private boolean isLocked = true;
public void lock(){
synchronized(this){
while(isLocked){
try{
this.wait();
} catch(InterruptedException e){
//do nothing, keep waiting
}
}
}
synchronized(this){
isLocked = true;
}
}
public synchronized void unlock(){
isLocked = false;
this.notify();
}
}
如果线程A先进入第一个lock同步块,这时候发现isLock为false。此时第二个线程执行,也进入第一个同步块,同样发现isLocked是false。此时两个线程都检查了这个条件,然后回继续进入到第二个同步快并设置isLocked为true。
修复方法:将isLocked = true移到第一个同步块中。
问题:变量的检查与赋值应该在同一个同步块中完成。