概述
什么线程通信假死?
即程序没有死锁,但是却卡住不执行了。对于线程间通信来说,就是所有的线程都wait了。
代码演示
public class Goods {
private final int MAX = 1;
private int goodsCount = 0;
public synchronized void produce() {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "获取了锁");
if (goodsCount >= MAX) {
try {
System.out.println(Thread.currentThread().getName() + "进行wait()");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
goodsCount++;
System.out.println(Thread.currentThread().getName() + "生产了第"+ goodsCount +"个商品,并notify");
notify();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void consume() {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "获取了锁");
if (goodsCount > 0) {
System.out.println(Thread.currentThread().getName() + "消费了第"+ goodsCount +"个商品,并notify");
goodsCount--;
notify();
} else {
System.out.println(Thread.currentThread().getName() + "进行wait()");
wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args) {
Goods goods = new Goods();
Arrays.asList("P1").forEach(p -> {
new Thread(p){
@Override
public void run() {
while (true){
goods.produce();
}
}
}.start();
});
Arrays.asList("C1").forEach(c -> {
new Thread(c){
@Override
public void run() {
while (true){
goods.consume();
}
}
}.start();
});
}
}
输入日志:
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
C1获取了锁
C1消费了第1个商品,并notify
C1获取了锁
C1进行wait()
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
C1获取了锁
C1消费了第1个商品,并notify
C1获取了锁
C1进行wait()
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
从输出结果来看,日志交错打印了生产者和消费者的信息。似乎没有什么问题。
改造一下生产者和消费者的数量:
public static void main(String[] args) {
Goods goods = new Goods();
Arrays.asList("P1", "P2").forEach(p -> {
new Thread(p) {
@Override
public void run() {
while (true) {
goods.produce();
}
}
}.start();
});
Arrays.asList("C1", "C2").forEach(c -> {
new Thread(c) {
@Override
public void run() {
while (true) {
goods.consume();
}
}
}.start();
});
}
输出日志:
......
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
C1获取了锁
C1消费了第1个商品,并notify
C1获取了锁
C1进行wait()
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
C1获取了锁
C1消费了第1个商品,并notify
C1获取了锁
C1进行wait()
P1获取了锁
P1生产了第1个商品,并notify
P1获取了锁
P1进行wait()
日志输出一段时间后就暂停了,难道死锁了吗?
可以通过:jstack 进程id 来查看应用程序是否发生了死锁:
C:\Users\asus>jps
13824 ShamDeadTest
C:\Users\asus>jstack 13824
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.77-b03 mixed mode):
"DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x0000000003382800 nid=0x492c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2" #15 prio=5 os_prio=0 tid=0x000000001fc0b800 nid=0x2dac in Object.wait() [0x00000000207de000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at java.lang.Object.wait(Object.java:502)
at com.thread.demo14.Goods.consume(Goods.java:41)
- locked <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at com.thread.demo14.ShamDeadTest$2.run(ShamDeadTest.java:30)
"C1" #14 prio=5 os_prio=0 tid=0x000000001fc0a800 nid=0x1aa0 in Object.wait() [0x00000000206de000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at java.lang.Object.wait(Object.java:502)
at com.thread.demo14.Goods.consume(Goods.java:41)
- locked <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at com.thread.demo14.ShamDeadTest$2.run(ShamDeadTest.java:30)
"P2" #13 prio=5 os_prio=0 tid=0x000000001fc09000 nid=0x33d0 in Object.wait() [0x00000000205de000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at java.lang.Object.wait(Object.java:502)
at com.thread.demo14.Goods.produce(Goods.java:17)
- locked <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at com.thread.demo14.ShamDeadTest$1.run(ShamDeadTest.java:17)
"P1" #12 prio=5 os_prio=0 tid=0x000000001fbff800 nid=0x1388 in Object.wait() [0x00000000204df000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at java.lang.Object.wait(Object.java:502)
at com.thread.demo14.Goods.produce(Goods.java:17)
- locked <0x000000076b6aa8e8> (a com.thread.demo14.Goods)
at com.thread.demo14.ShamDeadTest$1.run(ShamDeadTest.java:17)
从输出结果来看,程序并没有发生死锁。那具体是什么原因呢?
这是在代码中埋下的隐患:notify()和notifyAll()的问题。
notify()和notifyAll()
notify()会导致线程假死
notify()方法一次只能从等待队列中唤醒一个线程;notifyAll()能唤醒所有等待队列中的线程。
根据上图所示,当线程进行wait()时,线程会进入等待队列,并释放持有的锁。当进行notify()的时候,等待队列中的某一个线程才有可能被唤醒;如果是notifyAll(),则是等待队列中所有的线程都会被唤醒。
在上面的代码中,使用了notify()方式唤醒线程。那么会存在如下的运行可能:
说明:一开始,所有线程P1/P2、C1/C2都是在就绪状态,产品池容量为1:
(1)P1获取到了锁,进入product方法,生产了产品,产品满了:
打印:P1获取了锁;P1生产了第1个商品,并notify
(2)P2获取到了锁,进入product方法,此时商品已满:
打印:P2获取了锁;P2进行wait()
(3)C1获取到了锁,进入consume方法,此时有商品消费:
打印:C1获取了锁;C1消费了第1个商品,并notify
(4)C1获取到了锁,进入consume方法,此时没有商品消费:
打印:C1获取了锁;C1进行wait()
(5)C2获取到了锁,进入consume方法,此时没有商品消费:
打印:C2获取了锁;C2进行wait()
(6)P1获取到了锁,进入product方法,生产了产品,产品满了:
打印:P1获取了锁;P1生产了第1个商品,并notify
// 关键一步来了,此时等待状态的线程有:P2、C1、C2。这里是让P2线程获得唤醒
(7)P2获取到了锁,进入product方法,此时商品已满:
打印:P2获取了锁;P2进行wait()
(8)此时,还剩下P1还是就绪状态,所以P1获取到了锁,进入product方法,此时商品已满:
打印:P1获取了锁;P1进行wait()
(9)到此,程序中的P1/P2、C1/C2线程中,全部都是wait()状态,
全都线程都是wait()状态,程序停止活动了,就是假死了!
注:如果只有一个生产者,一个消费者,那么notify()也不会造成假死。可以自行分析。
notifyAll()不会导致线程假死
为什么说notifyAll()不会导致线程假死呢?
首先notifyAll()会唤醒等待队列中的所有等待状态的线程,将其状态有等待 -> 可运行。
只要有notifyAll()发生,那么程序会将等待状态的所有线程清空,所以都不会出现假死。