Java 线程间通讯信号的错失

起因

看《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(),进而引起死锁。
(如果错误,还请大佬指正…)

发布了24 篇原创文章 · 获赞 8 · 访问量 1871

猜你喜欢

转载自blog.csdn.net/qq_40462579/article/details/100937979