线程的创建、启动、停止
线程的创建方式:继承 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)