notify()¬ifyall()的共同点:均能唤醒正在等待的线程,并且均是最后只有一个线程获取资源对象的锁。
不同点:notify() 只能唤醒一个线程,而notifyall()能够唤醒所有的线程,当线程被唤醒以后所有被唤醒的线程竞争获取资源对象的锁,其中只有一个能够得到对象锁,执行代码。
注意:wait()方法并不是在等待资源的锁,而是在等待被唤醒(notify()),一旦被唤醒后,被唤醒的线程就具备了资源锁(因为无需竞争),直至再次执行wait()方法或者synchronized代码块执行完毕。
下文的代码由于只使用notify()没有使用notifyall()而导致出现死锁。
原因如下:假如A线程是sub线程,有两个线程B、C是main线程,刚开始isSub=true,则A线程首先获得当前对象的锁,A线程执行完毕后,执行notify()方法,这时候只唤醒了B,B执行完后,将isSub改为了true,此时B可能又通过notify()方法唤醒了C,则C由于执行main方法先会判断while(isSub),判断完后又进入了等待状态,这时候又没有任何一个线程来唤醒,因此出现了死锁。
而将notify改为notifyall()后,线程B进入等待状态,释放对象锁,这时候AC开始竞争获取对象锁,假如C获取对象锁,则C经过判断也进入等待状态,此时A开始工作。
从上述步骤中,我们也可以发现wait notify()通信机制的弊端,即:执行notify()方法的线程并不知道被唤醒的对象是谁。
public class OutTurn {
private boolean isSub = true;
private int count=0;
public synchronized void sub() {
try {
while (!isSub) {
this.wait();
}
System.out.println("sub ---- "+count);
isSub=false;
this.notify();
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
public synchronized void main() {
try {
while(isSub){
this.wait();
}
System.out.println("main (((((((((((( "+count);
isSub=true;
this.notify();
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
}
public class LockDemo {
public static void main(String[] args) {
final OutTurn ot = new OutTurn();
for(int j=0;j<100;j++){
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <5; i++) {
ot.sub();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
ot.main();
}
}
}).start();
}
}
}
永远在循环(loop)里调用 wait 和 notify,不是在 If 语句
现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。
基于以上认知,下面这个是使用wait和notify函数的规范代码模板:
- // The standard idiom for calling the wait method in Java
- synchronized (sharedObject) {
- while (condition) {
- sharedObject.wait();
- // (Releases lock, and reacquires on wakeup)
- }
- // do action based upon condition e.g. take or put into queue
- }
就像我之前说的一样,在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。
public class Business {
private boolean isSub = false;// 设置condition
public void main() {
for (int j = 0; j < 50; j++) {
synchronized (this) {
while (this.isSub)// 只要是子线程在运行,则一直等待 有时候会出现伪唤醒的情况 比如出现中断等情况
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.out.print("main:" + i + ",");
}
System.out.println();
this.isSub=true;//让子线程开始执行
this.notify();
}
}
}
public void sub() {
for (int j = 0; j < 50; j++) {
synchronized (this) {
while (!this.isSub) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.out.print("sub:" + i + ",");
}
System.out.println();
this.isSub=false;//让主线程开始执行
this.notify();
}
}
}
public static void main(String[] args) {
final Business b=new Business();
new Thread(
new Runnable(){
@Override
public void run() {
b.main();
}
}
).start();
new Thread(
new Runnable(){
@Override
public void run() {
b.sub();
}
}
).start();
}
}