起因
看《Thinking in Java》的过程中,在手搓第21.5.4节的 吐司BlockingQueue章节的代码时,不巧将代码敲错,从而发现了通讯信号的错失,个人认为比前一节信号错失的例子更直观(可能理解错误)。
代码
Toast.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:20
**/
public class Toast {
public enum Status{
DRY,
BUTTERED,
JAMMED
}
private Status status = Status.DRY;
private final int id;
public Toast(int id) {
this.id = id;
}
public void butter(){
this.status = Status.BUTTERED;
}
public synchronized void jam(){
this.status = Status.JAMMED;
Counter.addOne();
}
public Status getStatus() {
return status;
}
public int getId() {
return id;
}
public void setStatus(Status status) {
this.status = status;
}
@Override
public String toString() {
return "Toast{" +
"status=" + status +
", id=" + id +
'}';
}
}
ToastQueue.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:25
**/
public class ToastQueue extends LinkedBlockingDeque<Toast> {
private AtomicInteger putUseTimesOne = new AtomicInteger();
private AtomicInteger takeUseTimesOne = new AtomicInteger();
private AtomicInteger putUseTimesTwo = new AtomicInteger();
private AtomicInteger takeUseTimesTwo = new AtomicInteger();
private AtomicInteger putUseTimesThree = new AtomicInteger();
private AtomicInteger takeUseTimesThree = new AtomicInteger();
private AtomicInteger putUseTimesFour = new AtomicInteger();
private AtomicInteger takeUseTimesFour = new AtomicInteger();
public ToastQueue() {
putUseTimesOne.set(0);
takeUseTimesOne.set(0);
putUseTimesTwo.set(0);
takeUseTimesTwo.set(0);
putUseTimesThree.set(0);
takeUseTimesThree.set(0);
putUseTimesFour.set(0);
takeUseTimesFour.set(0);
}
@Override
public void put(Toast toast) throws InterruptedException {
super.put(toast);
if (Thread.currentThread().getName().equals("pool-1-thread-1")){
putUseTimesOne.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-2")){
putUseTimesTwo.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-3")){
putUseTimesThree.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-4")){
putUseTimesFour.incrementAndGet();
}
System.out.println(Thread.currentThread().getName() + "线程 调用ToastQueue#put()....");
}
@Override
public Toast take() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "线程 调用ToastQueue#take()....");
if (Thread.currentThread().getName().equals("pool-1-thread-1")){
takeUseTimesOne.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-2")){
takeUseTimesTwo.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-3")){
takeUseTimesThree.incrementAndGet();
}else if (Thread.currentThread().getName().equals("pool-1-thread-4")){
takeUseTimesFour.incrementAndGet();
}
if (Jammer.aBoolean){
return null;
}
return super.take();
}
public AtomicInteger getPutUseTimesOne() {
return putUseTimesOne;
}
public AtomicInteger getTakeUseTimesOne() {
return takeUseTimesOne;
}
public AtomicInteger getPutUseTimesTwo() {
return putUseTimesTwo;
}
public AtomicInteger getTakeUseTimesTwo() {
return takeUseTimesTwo;
}
public AtomicInteger getPutUseTimesThree() {
return putUseTimesThree;
}
public AtomicInteger getTakeUseTimesThree() {
return takeUseTimesThree;
}
public AtomicInteger getPutUseTimesFour() {
return putUseTimesFour;
}
public AtomicInteger getTakeUseTimesFour() {
return takeUseTimesFour;
}
}
Toaster.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:26
**/
public class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count = 0;
private Random random = new Random(47);
public Toaster(ToastQueue toasts) {
this.toastQueue = toasts;
}
@Override
public void run() {
try{
while (!Thread.interrupted()){
TimeUnit.MILLISECONDS.sleep(100 + random.nextInt(500));
Toast t = new Toast(count++);
System.out.println(Thread.currentThread().getName() + "线程 生产吐司:" + t);
toastQueue.put(t);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程 制作吐司被打断...");
}
System.out.println(Thread.currentThread().getName() + "线程 吐司制作完毕...");
}
}
Eater.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:38
**/
public class Eater implements Runnable{
private ToastQueue finishedQueue;
private int counter = 0;
public Eater(ToastQueue finishedQueue) {
this.finishedQueue = finishedQueue;
}
@Override
public void run() {
try{
while (!Thread.interrupted()){
Toast t = finishedQueue.take();
if (t.getId() != counter++ ||
t.getStatus() != Toast.Status.JAMMED){
System.out.println(Thread.currentThread().getName() + "线程 吃吐司发现吐司异常,停止食用...");
System.exit(0);
}else {
System.out.println(Thread.currentThread().getName() + "线程 成功吃掉吐司" + t.toString() + "....");
}
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程 准备吃吐司时被打断...");
}
System.out.println(Thread.currentThread().getName() + "线程 把吐司全部吃掉...");
}
}
Jammer.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:34
**/
public class Jammer implements Runnable {
public static boolean aBoolean = false;
private ToastQueue butteredQueue,finishedQueue;
private AtomicInteger butteredQueueUseWhileTimes = new AtomicInteger();
private AtomicInteger butteredQueueOverWhileTimes = new AtomicInteger();
public Jammer(ToastQueue butteredQueue, ToastQueue finishedQueue) {
this.butteredQueue = butteredQueue;
this.finishedQueue = finishedQueue;
this.butteredQueueUseWhileTimes.set(0);
}
@Override
public void run() {
try{
while (!Thread.interrupted()){
this.butteredQueueUseWhileTimes.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "线程进入 No." + butteredQueueUseWhileTimes.get() + " Jammer#run()-->while");
Toast t = butteredQueue.take();
t.jam();
System.out.println(Thread.currentThread().getName() + "线程 正在给" + t.toString() + "吐司涂果酱...");
finishedQueue.put(t);
butteredQueueOverWhileTimes.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "线程离开 No." + butteredQueueOverWhileTimes.get() + " Jammer#run()-->while");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程 准备给吐司涂果酱时被打断...");
if (!Thread.interrupted()){
System.out.println("Thread#interrupted()的状态监测并不及时!");
}
}
//以下代码造成死锁 原因?
//直接原因:错失了线程中断的信号,多调用了一次LinkedBlockingDeque#take(),导致了死锁。
//根本原因:Thread#interrupted()状态检查并不及时
//解决办法:try{while}..catch{}.. 或 在catch中添加break 或 在自定义的LinkedBlockingDeque类中声明信号量;
/*while (!Thread.interrupted()){
this.butteredQueueUseWhileTimes.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "线程进入 No." + butteredQueueUseWhileTimes.get() + " Jammer#run()-->while");
try {
//在第四次等待时被打断,进入catch块
Toast t = butteredQueue.take();
t.jam();
System.out.println(Thread.currentThread().getName() + "线程 正在给" + t.toString() + "吐司涂果酱...");
finishedQueue.put(t);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程 准备给吐司涂果酱时被打断...");
//解决方法二
//aBoolean = true;
//解决方法三
break;
}
butteredQueueOverWhileTimes.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "线程离开 No." + butteredQueueOverWhileTimes.get() + " Jammer#run()-->while");
}*/
System.out.println(Thread.currentThread().getName() + "线程 完成吐司涂果酱...");
}
public AtomicInteger getButteredQueueUseWhileTimes() {
return butteredQueueUseWhileTimes;
}
public AtomicInteger getButteredQueueOverWhileTimes() {
return butteredQueueOverWhileTimes;
}
}
ToastMatic.java
/**
* @program: Demo
* @description: BlockingQueueDemo
* @author: 郑畅道
* @create: 2019-09-15 19:42
**/
public class ToastMatic {
public static void main(String[] args) throws InterruptedException {
ToastQueue dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Toaster(dryQueue));
executorService.execute(new Butterer(dryQueue, butteredQueue));
Jammer jammer = new Jammer(butteredQueue, finishedQueue);
executorService.execute(jammer);
executorService.execute(new Eater(finishedQueue));
System.out.println(Thread.currentThread().getName() + "线程 准备请求关闭线程池....");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "线程 关闭线程池请求已发出....");
executorService.shutdownNow();
System.out.println("****************************************************************");
System.out.println("pool-1-thread-1线程 dryQueue ToastQueue#take()调用次数:" + dryQueue.getTakeUseTimesOne());
System.out.println("pool-1-thread-1线程 dryQueue ToastQueue#put()调用次数:" + dryQueue.getPutUseTimesOne());
System.out.println("****************************************************************");
System.out.println("pool-1-thread-2线程 dryQueue ToastQueue#take()调用次数:" + dryQueue.getTakeUseTimesTwo());
System.out.println("pool-1-thread-2线程 dryQueue ToastQueue#put()调用次数:" + dryQueue.getPutUseTimesTwo());
System.out.println("pool-1-thread-2线程 butteredQueue ToastQueue#take()调用次数:" + butteredQueue.getTakeUseTimesTwo());
System.out.println("pool-1-thread-2线程 butteredQueue ToastQueue#put()调用次数:" + butteredQueue.getPutUseTimesTwo());
System.out.println("****************************************************************");
System.out.println("pool-1-thread-3线程 butteredQueue ToastQueue#take()调用次数:" + butteredQueue.getTakeUseTimesThree());
System.out.println("pool-1-thread-3线程 butteredQueue ToastQueue#put()调用次数:" + butteredQueue.getPutUseTimesThree());
System.out.println("pool-1-thread-3线程 finishedQueue ToastQueue#take()调用次数:" + finishedQueue.getTakeUseTimesThree());
System.out.println("pool-1-thread-3线程 finishedQueue ToastQueue#put()调用次数:" + finishedQueue.getPutUseTimesThree());
System.out.println("pool-1-thread-3线程 finishedQueue 进入Jammer#run()--->while次数:" + jammer.getButteredQueueUseWhileTimes());
System.out.println("pool-1-thread-3线程 finishedQueue 退出Jammer#run()--->while次数:" + jammer.getButteredQueueOverWhileTimes());
System.out.println("Toast#jam() 使用次数:" + Counter.count);
System.out.println("****************************************************************");
System.out.println("pool-1-thread-4线程 finishedQueue ToastQueue#take()调用次数:" + finishedQueue.getTakeUseTimesFour());
System.out.println("pool-1-thread-4线程 finishedQueue ToastQueue#put()调用次数:" + finishedQueue.getPutUseTimesFour());
System.out.println("****************************************************************");
}
}
输出
正确输出
/*
out correct:
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-4线程 调用ToastQueue#take()....
main线程 准备请求关闭线程池....
pool-1-thread-3线程进入 No.1 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=0}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=0}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=0}吐司涂果酱...
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=0}....
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-3线程离开 No.1 Jammer#run()-->while
pool-1-thread-3线程进入 No.2 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=1}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=1}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=1}吐司涂果酱...
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=1}....
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-3线程离开 No.2 Jammer#run()-->while
pool-1-thread-3线程进入 No.3 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=2}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=2}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=2}吐司涂果酱...
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=2}....
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-3线程离开 No.3 Jammer#run()-->while
pool-1-thread-3线程进入 No.4 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
main线程 关闭线程池请求已发出....
pool-1-thread-3线程 准备给吐司涂果酱时被打断...
pool-1-thread-2线程 准备给吐司涂黄油时被打断...
pool-1-thread-1线程 制作吐司被打断...
pool-1-thread-2线程 完成吐司抹黄油...
pool-1-thread-4线程 准备吃吐司时被打断...
pool-1-thread-3线程 完成吐司涂果酱...
pool-1-thread-4线程 把吐司全部吃掉...
pool-1-thread-1线程 吐司制作完毕...
****************************************************************
pool-1-thread-1线程 dryQueue ToastQueue#take()调用次数:0
pool-1-thread-1线程 dryQueue ToastQueue#put()调用次数:3
****************************************************************
pool-1-thread-2线程 dryQueue ToastQueue#take()调用次数:4
pool-1-thread-2线程 dryQueue ToastQueue#put()调用次数:0
pool-1-thread-2线程 butteredQueue ToastQueue#take()调用次数:0
pool-1-thread-2线程 butteredQueue ToastQueue#put()调用次数:3
****************************************************************
pool-1-thread-3线程 butteredQueue ToastQueue#take()调用次数:4
pool-1-thread-3线程 butteredQueue ToastQueue#put()调用次数:0
pool-1-thread-3线程 finishedQueue ToastQueue#take()调用次数:0
pool-1-thread-3线程 finishedQueue ToastQueue#put()调用次数:3
pool-1-thread-3线程 finishedQueue 进入Jammer#run()--->while次数:4
pool-1-thread-3线程 finishedQueue 退出Jammer#run()--->while次数:3
****************************************************************
pool-1-thread-4线程 finishedQueue ToastQueue#take()调用次数:4
pool-1-thread-4线程 finishedQueue ToastQueue#put()调用次数:0
****************************************************************
dryQueue ToastQueue#take()调用次数:4
dryQueue ToastQueue#put()调用次数:3
butteredQueue ToastQueue#take()调用次数:4
butteredQueue ToastQueue#put()调用次数:3
finishedQueue ToastQueue#take()调用次数:4
finishedQueue ToastQueue#put()调用次数:3
Process finished with exit code 0
*/
错误输出
pool-1-thread-4线程 调用ToastQueue#take()....
main线程 准备请求关闭线程池....
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-3线程进入 No.1 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=0}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=0}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=0}吐司涂果酱...
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-3线程离开 No.1 Jammer#run()-->while
pool-1-thread-3线程进入 No.2 Jammer#run()-->while
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=0}....
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=1}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=1}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=1}吐司涂果酱...
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=1}....
pool-1-thread-3线程离开 No.2 Jammer#run()-->while
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-3线程进入 No.3 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 生产吐司:Toast{status=DRY, id=2}
pool-1-thread-1线程 调用ToastQueue#put()....
pool-1-thread-2线程 正在给吐司Toast{status=BUTTERED, id=2}抹黄油...
pool-1-thread-2线程 调用ToastQueue#put()....
pool-1-thread-3线程 正在给Toast{status=JAMMED, id=2}吐司涂果酱...
pool-1-thread-2线程 调用ToastQueue#take()....
pool-1-thread-4线程 成功吃掉吐司Toast{status=JAMMED, id=2}....
pool-1-thread-3线程 调用ToastQueue#put()....
pool-1-thread-4线程 调用ToastQueue#take()....
pool-1-thread-3线程离开 No.3 Jammer#run()-->while
pool-1-thread-3线程进入 No.4 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
main线程 关闭线程池请求已发出....
pool-1-thread-3线程 准备给吐司涂果酱时被打断...
pool-1-thread-3线程离开 No.4 Jammer#run()-->while
pool-1-thread-3线程进入 No.5 Jammer#run()-->while
pool-1-thread-3线程 调用ToastQueue#take()....
pool-1-thread-1线程 制作吐司被打断...
pool-1-thread-2线程 准备给吐司涂黄油时被打断...
****************************************************************
pool-1-thread-4线程 准备吃吐司时被打断...
pool-1-thread-2线程 完成吐司抹黄油...
pool-1-thread-1线程 吐司制作完毕...
pool-1-thread-4线程 把吐司全部吃掉...
pool-1-thread-1线程 dryQueue ToastQueue#take()调用次数:0
pool-1-thread-1线程 dryQueue ToastQueue#put()调用次数:3
****************************************************************
pool-1-thread-2线程 dryQueue ToastQueue#take()调用次数:4
pool-1-thread-2线程 dryQueue ToastQueue#put()调用次数:0
pool-1-thread-2线程 butteredQueue ToastQueue#take()调用次数:0
pool-1-thread-2线程 butteredQueue ToastQueue#put()调用次数:3
****************************************************************
pool-1-thread-3线程 butteredQueue ToastQueue#take()调用次数:5
pool-1-thread-3线程 butteredQueue ToastQueue#put()调用次数:0
pool-1-thread-3线程 finishedQueue ToastQueue#take()调用次数:0
pool-1-thread-3线程 finishedQueue ToastQueue#put()调用次数:3
pool-1-thread-3线程 finishedQueue 进入Jammer#run()--->while次数:5
pool-1-thread-3线程 finishedQueue 退出Jammer#run()--->while次数:4
****************************************************************
pool-1-thread-4线程 finishedQueue ToastQueue#take()调用次数:4
pool-1-thread-4线程 finishedQueue ToastQueue#put()调用次数:0
****************************************************************
dryQueue ToastQueue#take()调用次数:4
dryQueue ToastQueue#put()调用次数:3
butteredQueue ToastQueue#take()调用次数:5
butteredQueue ToastQueue#put()调用次数:3
finishedQueue ToastQueue#take()调用次数:4
finishedQueue ToastQueue#put()调用次数:3
Process stay running...
*/
结论
在线程被中断后,try…catch…很及时的捕获到,但是Thread#interrupted()并不能很及时的获取到线程的状态,从而导致了第5次进入while循环,然后第五次调用BlockingQueue#take(),进而引起死锁。
(如果错误,还请大佬指正…)