目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

线程的创建、启动、停止

线程的创建方式:继承 Thread

 run()整个都被重写

  • 任务与线程类高度耦合。
  • 每次新建任务都需要创建独立的线程,如果使用 Runnable 则可以利用线程池,大大减少创建线程和销毁线程的开销。
  • Java 只允许单继承,影响扩展性。
 1public class MyThread extends Thread {
 2    @Override
 3    public void run() {
 4        System.out.println(Thread.currentThread().getName());
 5    }
 6
 7    public static void main(String[] args) {
 8        MyThread myThread = new MyThread();
 9        myThread.setName("线程demo");
10        myThread.start();
11    }
12}

线程的创建方式:实现 Runnable

 最终调用 target.run()

  • 任务与线程类解耦
  • 可扩展
  • 解决资源开销(利用线程池)
 1public class MyRunable implements Runnable,Serializable {
 2    @Override
 3    public void run() {
 4        System.out.println(Thread.currentThread().getName());
 5        try {
 6            System.in.read();
 7        } catch (IOException e) {
 8            e.printStackTrace();
 9        }
10    }
11
12    public static void main(String[] args) {
13        Thread thread = new Thread(new MyRunable());
14        thread.setName("soulboy");
15        thread.start();  // 开启新的线程执行  soulboy
16//        thread.run();   // 在当前线程下执行 main
17    }
18}

同时用两种方法会怎样?

 由于继承 Thread 类并且重写 run() 方法, 会覆盖掉 Thread 类原有的 run()方法。
 Thread 类原有 run() 方法用于调用 target.run() 来执行传入 Runable 对象的 run() ,由于继承 Thread 时被覆盖了,所以 Runable 对象的 run() 得不到调用。
 最终输出结果: 我来自 Thread

1@Override
2    public void run() {
3        if (target != null) {
4            target.run();
5        }

 基于 Runnable 接口的匿名内部类,再重写了 run()方法。

 1/**
 2 * 描述:同时使用Runnable和Thread两种实现线程的方式
 3 */
 4public class BothRunnableThread extends Thread implements Runnable{
 5
 6    public static void main(String[] args) {
 7        new Thread(new Runnable() {
 8            @Override
 9            public void run() {
10                System.out.println("我来自Runnable");
11            }
12        }) {
13            @Override
14            public void run() {
15                System.out.println("我来自Thread");
16            }
17        }.start();
18
19/*        Thread thread1 = new Thread(new BothRunnableThread());
20        thread1.start();
21
22        Thread thread2 = new BothRunnableThread();
23        thread2.start();*/
24
25    }
26
27/*    @Override
28    public void run() {
29        System.out.println("继承Thread");
30    }*/
31}

创建线程的方式有几种

 创建线程的方式只有一种方式:就是构造 Thread 的类,Thread 本身实现了 Runable 接口。
 实现线程的执行单元有两种方式。

1* extends Thread
2* implements Runnable

实现 Runable:匿名内部类

 1/**
 2 * 匿名内部类的方式
 3 */
 4public class MyThread {
 5    public static void main(String[] args) {
 6        Thread thread = new Thread(new Runnable() {
 7            @Override
 8            public void run() {
 9                System.out.println(Thread.currentThread().getName());
10            }
11        });
12        thread.start();
13    }
14}

实现 Runable:Lambda 表达式

1public class Lambda {
2    public static void main(String[] args) {
3        new Thread(() -> {
4            System.out.println(Thread.currentThread().getName());
5        }).start();
6    }
7}

实现 Runable:线程池

1public class ThreadPool {
2    public static void main(String[] args) {
3        ExecutorService executorService = Executors.newSingleThreadExecutor();
4        executorService.execute(()->{
5            System.out.println(Thread.currentThread().getName());//pool-1-thread-1
6        });
7    }
8}

start() 和 run() 的比较

 调用 start() 方法之后,运行中的 main 线程会通知 JVM 在空闲时候启动新线程。线程什么时候执行由线程调度器所决定,无法保证立刻启动新线程并执行。(线程饥饿的情况) new Thread(runnable).start() 指令本身是由主线程执行的。
 新线程的准备工作

  • 就绪状态:已获取除 CPU 之外的其他资源:上下文、栈、线程状态、寄存器等…
  • 执行状态:等待获取 CPU 资源。
  • 运行状态:执行 run() 方法中代码逻辑。
1* run() 不会开启新的执行路径,只是普通的方法,用于添加执行线程逻辑,但其本身并不会开启新的线程,所以依然是在主线程下按顺序执行
2* start() 会开启新的执行路径(新线程)
 1package com.xdclass.couponapp.test.start;
 2
 3/**
 4 * 对比 start & run
 5 */
 6public class StartAndRunMethod {
 7    public static void main(String[] args) {
 8        Runnable runnable = () -> {
 9            System.out.println(Thread.currentThread().getName());//main
10        };
11        runnable.run();
12        new Thread(runnable).start();//Thread-0
13        new Thread(runnable).run();//main
14    }
15}

连续两次调用 start() 方法

Exception in thread "main" java.lang.IllegalThreadStateException
 线程生命周期无法逆转,无法从 TERMINATED 状态变为 NEW 状态。

源码分析

  • 启动新线程检查线程状态,0 代表初始化尚未启动的状态。
1if (threadStatus != 0)
2            throw new IllegalThreadStateException();
  • 加入线程组
  • 调用 start0() native 方法
 1package com.xdclass.couponapp.test.start;
 2
 3public class CantStartTwice {
 4    public static void main(String[] args) {
 5        Thread thread = new Thread(
 6                () -> System.out.println(Thread.currentThread().getName())
 7        );
 8        thread.start();
 9        thread.start();
10    }
11}

如何正确停止线程

  • 原理:用 interrupt 来请求的优点
  • 想终止线程,需要请求方、被停止方、子方法被调用方相互配置。
  • 错误方法:stop/suspend 已废弃、volatile 的 boolean 无法处理长时间阻塞的情况。

应该使用 interrupt 来通知,而不是强制使用 stop()
 正确的停止线程:如何正确的使用 interrupt 来通知线程,以及被停止的线程如何配合 interrupt。
 想让线程快速、安全、可靠的停止下来并不容易,Java 没有一种机制安全正确的停止线程。
 Java 提供了 interrupt ,一种合作机制(使用一个线程通知另外一个线程,让它停止工作)。
 被通知要 interrupt 的线程自身拥有最终决定权:被 interrupt 线程本身更加了解自己的业务状态,而不是通知 interrupt 线程。

使用 interrupt 的优点

  • 被中断的线程自身拥有如何响应中断的权利(有些线程的某些代码必须具备原子性,必须要等待这些写成处理完成之后,或是准备好之后),再由它们自己主动终止。
  • 被中断线程可以完全不理会中断(如果有需要的话),不应该鲁莽的使用 stop()方法,而是 interrupt()
  • 需要遵循良好编码规范:让被告知中断的线程自身可以响应中断。

响应中断的方法列表
 以下方法可以响应 interrupt,响应的方式为抛出 InterruptedException

 1* Object.wait()  
 2* Thread.sleep()  
 3* Thread.join  
 4* java.util.concurrent.BlockingQueue.take() /put()  
 5* java.util.concurrent.locks.Lock.lockInterruptibly()  
 6* java.util.concurrent.CountDownLatch.await()  
 7* java.util.concurrent.CyclicBarrier.await()  
 8* java.util.concurrent.Exchanger.exchange(V)  
 9* java.nio.channels.InterruptibleChannel相关方法  
10* java.nio.channels.Selector的相关方法

最佳实践:普通情况下如何停止线程

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl;
 4
 5/**
 6 * run() 内没有 sleep() 或 wait() 时,停止线程
 7 */
 8public class RightWayStopThreadWithoutSleep implements Runnable {
 9
10    @Override
11    public void run() {
12        int num = 0;
13        //添加线程可以响应中断的判断逻辑,否则线程不会理会interrupt,直到循环结束。
14        while ( !Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE /2){
15            if(num % 10000 == 0){
16                System.out.println(num + "是10000的倍数.");
17            }
18            num++;
19        }
20        System.out.println("任务运行结束了.");
21    }
22
23    public static void main(String[] args) throws InterruptedException {
24        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
25        thread.start();
26        Thread.sleep(1000);
27        thread.interrupt();
28    }
29}
30
1613930000是10000的倍数.  
2613940000是10000的倍数.  
3任务运行结束了.

最佳实践:线程被阻塞情况下停止线程
 当线程处于休眠的过程中,如果接收到 interrupt 信号,响应 interrupt 的方式是抛出 java.lang.InterruptedException: sleep interrupted 异常。
 中断线程的写法:catch() 捕获异常,响应 interrupt 信号。

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 带有 sleep() 的中断线程的写法
 5 */
 6public class RightWayStopThreadWithSleep {
 7    public static void main(String[] args) throws InterruptedException {
 8        //匿名内部类(基于接口)
 9        Runnable runnable = () -> {
10            int num = 0;
11            try {
12                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
13                    if (num % 100 == 0) {
14                        System.out.println(num + "是100的倍数");
15                    }
16                    num++;
17                }
18                Thread.sleep(1000);
19            } catch (InterruptedException e) {
20                e.printStackTrace();
21            }
22        };
23
24        //启动线程
25        Thread thread = new Thread(runnable);
26        thread.start();
27        //在线程处于休眠状态时:发起 interrupt
28        Thread.sleep(500);
29        thread.interrupt();
30
31    }
32
33}
10是100的倍数  
2100是100的倍数  
3200是100的倍数  
4300是100的倍数  
5java.lang.InterruptedException: sleep interrupted  
6 at java.lang.Thread.sleep(Native Method)  
7 at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18)  
8 at java.lang.Thread.run(Thread.java:748)

最佳实践:如果线程在每次迭代后都阻塞

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 如果在执行过程中,每次循环都会 sleep() 或 wait() ...
 5 * 那么不需要每次都迭代都检查 Thread.currentThread().isInterrupted()
 6 * 因为 sleep() 过程中的线程接收到 interrupt 消息会抛出异常,因此无需检查
 7 * 只需捕获处理即可
 8 */
 9public class RightWayStopThreadWithSleepEveryLoop {
10
11    public static void main(String[] args) throws InterruptedException {
12        //匿名内部类(基于接口)
13        Runnable runnable = () -> {
14            int num = 0;
15            try {
16                while (num <= 10000) {
17                    if (num % 100 == 0) {
18                        System.out.println(num + "是100的倍数");
19                    }
20                    num++;
21                    Thread.sleep(10);
22                }
23            } catch (InterruptedException e) {
24                e.printStackTrace();
25            }
26        };
27
28        //启动线程
29        Thread thread = new Thread(runnable);
30        thread.start();
31        //在线程处于休眠状态时:发起 interrupt
32        Thread.sleep(5000);
33        thread.interrupt();
34    }
35}
36
10是100的倍数
2100是100的倍数
3200是100的倍数
4300是100的倍数
5400是100的倍数
6java.lang.InterruptedException: sleep interrupted

try/catch 在 while 中会导致中断失效
 不要在 while 循环中 使用 try/catch 处理 sleep() 对 interrupt 的响应,中断的效果会失效。

1* 问什么会一直循环?
2   答:interrupt 被捕获在循环内部,并没有出错,因此循环会继续。
3* 为什么 Thread.currentThread().isInterrupted() 判断不生效?
4   答:sleep() 响应 interrupt 时候除了抛出异常,还会一并清除线程的 isInterrupted 标记位。
 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 如果 while 循环中使用 try/catch 响应中断,则会导致中断失效。
 5 */
 6public class CantInterrupt {
 7    public static void main(String[] args) throws InterruptedException {
 8        Runnable runnable = () -> {
 9            int num = 0;
10            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
11                if (num % 100 == 0) {
12                    System.out.println(num + "是100的倍数.");
13                }
14                num++;
15                try {
16                    Thread.sleep(10);
17                } catch (InterruptedException e) {
18                    e.printStackTrace();
19                }
20            }
21        };
22
23        //启动线程
24        Thread thread = new Thread(runnable);
25        thread.start();
26        //在线程处于休眠状态时:发起 interrupt
27        Thread.sleep(5000);
28        thread.interrupt();
29
30    }
31}
32
 10是100的倍数.
 2100是100的倍数.
 3200是100的倍数.
 4300是100的倍数.
 5400是100的倍数.
 6java.lang.InterruptedException: sleep interrupted
 7	at java.lang.Thread.sleep(Native Method)
 8	at com.xdclass.couponapp.test.stopThreads.CantInterrupt.lambda$main$0(CantInterrupt.java:16)
 9	at java.lang.Thread.run(Thread.java:748)
10500是100的倍数.
11600是100的倍数.

实际开发中的两种最佳实践

优先选择:传递中断 (方法的签名上抛出异常)

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 最佳实践 : catch 了 InterruptedException 之后优先选择:在方法签名中抛出异常。
 5 * 那么在 run() 就会强制 try/catch
 6 */
 7public class RightWayStopThreadInProd implements Runnable{
 8
 9    @Override
10    public void run() {
11        //try嵌套在while循环外面的意义是:中断while循环(否则只会抛出异常,while会继续循环)
12        try {
13            while (true) {
14                System.out.println("go");
15                throwInMethod();
16            }
17        } catch (InterruptedException e) {
18            //保存日志 ....
19            e.printStackTrace();
20        }
21    }
22
23    /**
24     * 如果此方法是为其他线程做调用,一定要选择抛出异常,而不是在内部做异常处理
25     * 这样可以防止本方法吞噬 interrupt 
26     * 应该把处理 interrupt 的方式选择器 交给上层的调用者
27     * @throws InterruptedException
28     */
29    private void throwInMethod() throws InterruptedException {
30        Thread.sleep(2000);
31    }
32
33    public static void main(String[] args) throws InterruptedException {
34        Thread thread = new Thread(new RightWayStopThreadInProd());
35        thread.start();
36        Thread.sleep(1000);
37        thread.interrupt();
38    }
39}

不想或无法传递:恢复中断

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 最佳实践2 : 在 catch 子句中调用Thread.currentThread().interrupt() 来回复设置中断状态
 5 * 以便在后续的执行中,让调用者能够检查到刚才发生的中断。
 6 */
 7public class RightWayStopThreadInProd2 implements Runnable {
 8    @Override
 9    public void run() {
10        while (true) {
11            System.out.println("go");
12            reInterrupt();
13            if (Thread.currentThread().isInterrupted()){
14                //记录日志
15                System.out.println("reInterrupt 方法运行期间发生中断!运行结束");
16                break;
17            }
18            System.out.println("go 2");
19        }
20    }
21
22    /**
23     *  虽然 catch 住了 interrrupt 引发的异常
24     *  但是没有独吞,而且是重新恢复了 interrupt
25     * @throws InterruptedException
26     */
27    private void reInterrupt() {
28        try {
29            Thread.sleep(1000);
30        } catch (InterruptedException e) {
31            //重新设置 interrupt
32            Thread.currentThread().interrupt();
33            e.printStackTrace();
34        }
35    }
36
37    public static void main(String[] args) throws InterruptedException {
38        Thread thread = new Thread(new RightWayStopThreadInProd2());
39        thread.start();
40        Thread.sleep(2000);
41        thread.interrupt();
42    }
43}
1go
2go 2
3go
4java.lang.InterruptedException: sleep interrupted
5	at java.lang.Thread.sleep(Native Method)
6	at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.reInterrupt(RightWayStopThreadInProd2.java:29)
7	at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.run(RightWayStopThreadInProd2.java:12)
8	at java.lang.Thread.run(Thread.java:748)
9reInterrupt 方法运行期间发生中断!运行结束

不应该屏蔽中断
 独吞中断,并且不恢复中断。

停止线程相关重要函数解析

interrupt() 方法为什么可以中断处于 wait 状态的线程?
 追踪源码直至 private native void interrupt0(); (本地方法)
 进入 github(也可以进 OpenJDK 网站)

1* 设置 interrupted 状态为 true
2* _SleepEvent 对应 Thread.sleep()
3…… 看不懂

判断是否已经被中断的相关方法

1# 用于判断当前线程中断状态的同时,清除线程中断状态
2static boolean interrupted():返回当前线程是否已经被中断状态,如果是则返回 true,之后会把线程的中断状态设置为 false。
3# 用于判断线程中断状态
4boolean isInterrupted():返回当前线程是否已经被中断。
 1package com.xdclass.couponapp.test.volatiledemo;
 2
 3/**
 4 * 描述:注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象
 5 */
 6public class RightWayInterrupted {
 7
 8    public static void main(String[] args) throws InterruptedException {
 9
10        Thread threadOne = new Thread(new Runnable() {
11            @Override
12            public void run() {
13                for (; ; ) {
14                }
15            }
16        });
17
18        // 启动线程
19        threadOne.start();
20        //设置中断标志 (threadOne线程被设置为中断)
21        threadOne.interrupt();
22        //获取中断标志
23        System.out.println("isInterrupted: " + threadOne.isInterrupted()); //true (threadOne线程)
24        //获取中断标志并重置
25        System.out.println("isInterrupted: " + threadOne.interrupted()); //false  静态方法(main线程)
26        //获取中断标志并重直
27        System.out.println("isInterrupted: " + Thread.interrupted()); //false 静态方法(main线程)
28        //获取中断标志
29        System.out.println("isInterrupted: " + threadOne.isInterrupted());//true (threadOne线程)
30        threadOne.join();
31        System.out.println("Main thread is over.");
32    }
33}

如何处理不可中断阻塞

 没有银弹!!!根据特定情况使用特定的方法。

常见错误观点

线程池创建线程也算一种新建线程的方式

1* 并不是构建线程的本质
2* 查看源码发现线程池也是通过 new Thread(Runnable r)  和 new Thread() 。
 1package com.xdclass.couponapp.test;
 2
 3import java.util.concurrent.ExecutorService;
 4import java.util.concurrent.Executors;
 5
 6public class ThradPool5 {
 7    public static void main(String[] args) {
 8        ExecutorService executorService = Executors.newCachedThreadPool();
 9        for (int i = 0; i < 1000; i++) {
10            executorService.submit(new Task());
11        }
12    }
13
14}
15
16class Task implements Runnable {
17    @Override
18    public void run() {
19        try {
20            Thread.sleep(500);
21        } catch (InterruptedException e) {
22            e.printStackTrace();
23        }
24        System.out.println(Thread.currentThread().getName());
25    }
26}
1pool-1-thread-977
2pool-1-thread-980
3pool-1-thread-1000

通过 Callable 和 FutureTask 创建线程,也算是一种新的创建线程的方式

1* 并不是构建线程的本质
2* 查看源码发现 FutureTask 实现了 RunnableFutrue,  RunnableFutrue 同时继承了 Runnable 、Future 两个接口。
3* 所以本质是离不开 Runnable 接口的。

无返回值是实现 Runnable 接口,有返回值是实现 Callable 接口,所以 Callable 是新的实现线程的方式

1* 并不是构建线程的本质
2* 同上

定时器也是一种创建线程的方式

 1package com.xdclass.couponapp.test;
 2
 3import java.util.Timer;
 4import java.util.TimerTask;
 5
 6public class DemoTimerTask {
 7    public static void main(String[] args) {
 8        Timer timer = new Timer();
 9        //每个一秒钟打印一次
10        timer.scheduleAtFixedRate(new TimerTask() {
11            @Override
12            public void run() {
13                System.out.println(Thread.currentThread().getName());
14            }
15        },1000,1000);
16    }
17}
1Timer-0
2Timer-0

匿名你内部类、Lambda 表达式也是一种创建线程的方式

1* 很明显本质还是通过构建Thread,只是语法层面的蜜糖。
 1package com.xdclass.couponapp.test;
 2
 3public class AnonymousInnerClassDemo {
 4    public static void main(String[] args) {
 5        //匿名内部类
 6        new Thread() {
 7            @Override
 8            public void run() {
 9                System.out.println(Thread.currentThread().getName());//Thread-0
10            }
11        }.start();
12
13        //匿名内部类(基于接口)
14        new Thread(new Runnable() {
15            @Override
16            public void run() {
17                System.out.println(Thread.currentThread().getName());//Thread-1
18            }
19        }).start();
20
21        //Lambda
22        new Thread(
23                () -> System.out.println(Thread.currentThread().getName())
24        ).start();//Thread-2
25    }
26}

如何正确的停止线程?

  • stop、suspend、resume 方法
1* stop 野蛮、它本质上不是安全的。停止线程会导它解锁已所以定的所有监视器。
2* suspend、resueme已经废弃

stop 示例

 1package com.xdclass.couponapp.test.stopThreads;
 2
 3/**
 4 * 错误的停止线程的方法: 用 stop() 来停止线程,会导致线程运行一半突然停止
 5 * 没办法完成一个基本单元的操作(一个连队),会造成脏数据(有的连队多领、有的连队少领)
 6 * 领取弹药
 7 * 其中有一个连队  居然有些人没有领取到(连队本身丢失了原子性)
 8 * 1连队0号士兵
 9 * 1连队1号士兵
10 * 1连队2号士兵
11 * 1连队3号士兵
12 * 1连队4号士兵
13 * 1连队5号士兵
14 * 1连队6号士兵
15 */
16public class StopThread implements Runnable{
17    @Override
18    public void run() {
19        //一共5个连队,每队10人,以连队为单位,方法武器弹药,叫到号的士兵前去领取。
20        for (int i = 0; i < 5; i++) {
21            System.out.println("连队" + i + "开始领取武器");
22            for (int j = 0; j < 10; j++) {
23                System.out.println(i + "连队" + j + "号士兵");
24                try {
25                    Thread.sleep(60);
26                } catch (InterruptedException e) {
27                    e.printStackTrace();
28                }
29            }
30            System.out.println(i + "连队完成领取工作");
31        }
32    }
33    public static void main(String[] args) {
34        Thread thread = new Thread(new StopThread());
35        thread.start();
36        try {
37            Thread.sleep(1000);
38        } catch (InterruptedException e) {
39            e.printStackTrace();
40        }
41        thread.stop();
42    }
43}
  • 用 volatile 设置 boolean 标记位

 volatile 示例(看上去似乎是可行的)

 1package com.xdclass.couponapp.test.volatiledemo;
 2
 3import sun.tools.jconsole.Worker;
 4
 5/**
 6 * 演示用 volatile 的局限。
 7 *
 8 * 0是100的倍数
 9 * 100是100的倍数
10 * 200是100的倍数
11 * 300是100的倍数
12 * 400是100的倍数
13 */
14public class WrongWayVolatile implements Runnable{
15    //让变量具有可见性
16    private volatile boolean canceled = false;
17
18    @Override
19    public void run() {
20        int num = 0;
21        try {
22            while (num <= 100000 && !canceled) {
23                if (num % 100 == 0) {
24                    System.out.println(num + "是100的倍数");
25                }
26                num++;
27                Thread.sleep(10);
28            }
29        } catch (InterruptedException e) {
30            e.printStackTrace();
31        }
32    }
33
34    public static void main(String[] args) throws InterruptedException {
35        WrongWayVolatile r = new WrongWayVolatile();
36        Thread thread = new Thread(r);
37        thread.start();
38        Thread.sleep(5000);
39        r.canceled = true;
40    }
41}
42

 volatile 示例(事实证明有局限性,无法在想要停止的时候停止线程)
 当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
 以下示例发现无法终止生产者,生产者一直在运行,阻塞在队列上。

 1package com.xdclass.couponapp.test.volatiledemo;
 2
 3
 4import java.util.concurrent.ArrayBlockingQueue;
 5import java.util.concurrent.BlockingDeque;
 6import java.util.concurrent.BlockingQueue;
 7
 8/**
 9 * 当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
10 * 生产者的生产速度快,消费者消费速度慢
11 * 阻塞队列满以后,生产者会阻塞,等待消费者进一步消费
12 */
13public class WrongWayVolatileCantStop {
14    public static void main(String[] args) throws InterruptedException {
15        //队列特点:满的时候放不进去,空的时候取会阻塞。
16        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
17
18        //生产者
19        Producer producer = new Producer(storage);
20        Thread producerThread = new Thread(producer);
21        producerThread.start();
22        Thread.sleep(1000);
23
24        //消费者
25        Consumer consumer = new Consumer(storage);
26        while (consumer.needMoreNums()) {
27            System.out.println(consumer.storage.take() + "已经被消费");
28            Thread.sleep(100);
29        }
30        System.out.println("消费者不需要更多数据了");
31        //一旦消费不需要更多数据了,就让生产者停止,但是实际情况
32        producer.canceled = true;
33        System.out.println(producer.canceled);
34    }
35}
36
37class Producer implements Runnable {
38    //让变量具有可见性
39    public volatile boolean canceled = false;
40
41    //队列
42    BlockingQueue storage;
43
44    public Producer(BlockingQueue storage) {
45        this.storage = storage;
46    }
47
48    @Override
49    public void run() {
50        int num = 0;
51        try {
52            while (num <= 100000 && !canceled) {
53                if (num % 100 == 0) {
54                    //这里如果队列已经满,会导致线程一直处于阻塞状态
55                    //队列被消费(少于10个)才会唤醒生产者线程
56                    //如果队列一直没有被消费,那么永远不会执行到while循环的判断条件 !canceled
57                    //所以就算是canceled已经被设置为 true,被阻塞在队列的生产者线程也无法被唤醒
58                    //将一直处于阻塞状态
59                    storage.put(num);
60                    System.out.println(num + "是100的倍数,被放到仓中了");
61                }
62                num++;
63            }
64        } catch (InterruptedException e) {
65            e.printStackTrace();
66        } finally {
67            System.out.println("生者停止运行");
68        }
69    }
70}
71
72class Consumer {
73    //队列
74    BlockingQueue storage;
75
76    public Consumer(BlockingQueue storage) {
77        this.storage = storage;
78    }
79
80    public boolean needMoreNums(){
81        if (Math.random() > 0.95) {
82            return false;
83        }
84        return true;
85    }
86}

 用 interrupt() 代替 volatile 示例(还是 interrupt() 靠谱,修复版)

 1package com.xdclass.couponapp.test.volatiledemo;
 2
 3import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
 4
 5import java.util.concurrent.ArrayBlockingQueue;
 6import java.util.concurrent.BlockingQueue;
 7
 8/**
 9 * 描述:     用中断来修复刚才的无尽等待问题
10 * 想要实例化内部类,需要先要实例化外部类,这里仅仅是复习知识而已,仅此而已。
11 * 为什么要在抛出InterruptedException的时候清除掉中断状态呢?
12 * 这个问题没有找到官方的解释,估计只有Java设计者们才能回答了。
13 * 但这里的解释似乎比较合理:一个中断应该只被处理一次(你catch了这个InterruptedException,
14 * 说明你能处理这个异常,你不希望上层调用者看到这个中断)。
15 */
16public class WrongWayVolatileFixed {
17
18    public static void main(String[] args) throws InterruptedException {
19        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
20        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
21
22        Producer producer = body.new Producer(storage);
23        Thread producerThread = new Thread(producer);
24        producerThread.start();
25        Thread.sleep(1000);
26
27        Consumer consumer = body.new Consumer(storage);
28        while (consumer.needMoreNums()) {
29            System.out.println(consumer.storage.take() + "被消费了");
30            Thread.sleep(100);
31        }
32        System.out.println("消费者不需要更多数据了。");
33
34        producerThread.interrupt();
35        System.out.println(producerThread.isInterrupted()); //true
36    }
37
38
39    class Producer implements Runnable {
40
41        BlockingQueue storage;
42
43        public Producer(BlockingQueue storage) {
44            this.storage = storage;
45        }
46
47        /**
48         * 事实证明:除了 sleep()  其他被阻塞线程在响应 interrupt 的时候 也会清空 isInterrupted 标志位
49         * 所以这里 使用 isInterrupted() 作为判断条件是毫无意义的
50         * 需要借助 try.catch  套在 while 外面来中断 while 循环
51         */
52        @Override
53        public void run() {
54            int num = 0;
55            try {
56                //以下写法毫无意义:因为阻塞线程响应中断的时候会重置 isInterrupted 的值
57                //while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
58                while (num <= 100000) {
59                    if (num % 100 == 0) {
60                        storage.put(num);
61                        System.out.println(num + "是100的倍数,被放到仓库中了。");
62                        System.out.println(Thread.currentThread().isInterrupted());
63                    }
64                    num++;
65                }
66            } catch (InterruptedException e) {
67                e.printStackTrace();
68                System.out.println(Thread.currentThread().isInterrupted());
69            } finally {
70                System.out.println("生产者结束运行");
71                System.out.println(Thread.currentThread().isInterrupted());
72            }
73        }
74    }
75
76    class Consumer {
77
78        BlockingQueue storage;
79
80        public Consumer(BlockingQueue storage) {
81            this.storage = storage;
82        }
83
84        public boolean needMoreNums() {
85            if (Math.random() > 0.95) {
86                return false;
87            }
88            return true;
89        }
90    }
91}
92
 14900被消费了
 25900是100的倍数,被放到仓库中了。
 3false
 45000被消费了
 56000是100的倍数,被放到仓库中了。
 6false
 7消费者不需要更多数据了。
 8true
 9false
10生产者结束运行
11false
12java.lang.InterruptedException
13	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
14	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
15	at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
16	at com.xdclass.couponapp.test.volatiledemo.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:56)
17	at java.lang.Thread.run(Thread.java:748)

作者:Soulboy