线程的创建、启动、停止
线程的创建方式:继承 Thread
run()整个都被重写
- 任务与线程类高度耦合。
- 每次新建任务都需要创建独立的线程,如果使用 Runnable 则可以利用线程池,大大减少创建线程和销毁线程的开销。
- Java 只允许单继承,影响扩展性。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("线程demo");
myThread.start();
}
}
线程的创建方式:实现 Runnable
最终调用 target.run()
- 任务与线程类解耦
- 可扩展
- 解决资源开销(利用线程池)
public class MyRunable implements Runnable,Serializable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunable());
thread.setName("soulboy");
thread.start(); // 开启新的线程执行 soulboy
// thread.run(); // 在当前线程下执行 main
}
}
同时用两种方法会怎样?
由于继承 Thread 类并且重写 run() 方法, 会覆盖掉 Thread 类原有的 run()方法。
Thread 类原有 run() 方法用于调用 target.run() 来执行传入 Runable 对象的 run() ,由于继承 Thread 时被覆盖了,所以 Runable 对象的 run() 得不到调用。
最终输出结果: 我来自 Thread
@Override
public void run() {
if (target != null) {
target.run();
}
 基于 Runnable 接口的匿名内部类,再重写了 run()方法。
/**
* 描述:同时使用Runnable和Thread两种实现线程的方式
*/
public class BothRunnableThread extends Thread implements Runnable{
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我来自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我来自Thread");
}
}.start();
/* Thread thread1 = new Thread(new BothRunnableThread());
thread1.start();
Thread thread2 = new BothRunnableThread();
thread2.start();*/
}
/* @Override
public void run() {
System.out.println("继承Thread");
}*/
}
创建线程的方式有几种
创建线程的方式只有一种方式:就是构造 Thread 的类,Thread 本身实现了 Runable 接口。
实现线程的执行单元有两种方式。
* extends Thread
* implements Runnable
实现 Runable:匿名内部类
/**
* 匿名内部类的方式
*/
public class MyThread {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
thread.start();
}
}
实现 Runable:Lambda 表达式
public class Lambda {
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}).start();
}
}
实现 Runable:线程池
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());//pool-1-thread-1
});
}
}
start() 和 run() 的比较
调用 start() 方法之后,运行中的 main 线程会通知 JVM 在空闲时候启动新线程。线程什么时候执行由线程调度器所决定,无法保证立刻启动新线程并执行。(线程饥饿的情况) new Thread(runnable).start() 指令本身是由主线程执行的。
新线程的准备工作
- 就绪状态:已获取除 CPU 之外的其他资源:上下文、栈、线程状态、寄存器等…
- 执行状态:等待获取 CPU 资源。
- 运行状态:执行 run() 方法中代码逻辑。
* run() 不会开启新的执行路径,只是普通的方法,用于添加执行线程逻辑,但其本身并不会开启新的线程,所以依然是在主线程下按顺序执行
* start() 会开启新的执行路径(新线程)
package com.xdclass.couponapp.test.start;
/**
* 对比 start & run
*/
public class StartAndRunMethod {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());//main
};
runnable.run();
new Thread(runnable).start();//Thread-0
new Thread(runnable).run();//main
}
}
连续两次调用 start() 方法
Exception in thread "main" java.lang.IllegalThreadStateException
线程生命周期无法逆转,无法从 TERMINATED 状态变为 NEW 状态。
源码分析
- 启动新线程检查线程状态,0 代表初始化尚未启动的状态。
if (threadStatus != 0)
throw new IllegalThreadStateException();
- 加入线程组
- 调用 start0() native 方法
package com.xdclass.couponapp.test.start;
public class CantStartTwice {
public static void main(String[] args) {
Thread thread = new Thread(
() -> System.out.println(Thread.currentThread().getName())
);
thread.start();
thread.start();
}
}
如何正确停止线程
- 原理:用 interrupt 来请求的优点
- 想终止线程,需要请求方、被停止方、子方法被调用方相互配置。
- 错误方法:stop/suspend 已废弃、volatile 的 boolean 无法处理长时间阻塞的情况。
应该使用 interrupt 来通知,而不是强制使用 stop()
正确的停止线程:如何正确的使用 interrupt 来通知线程,以及被停止的线程如何配合 interrupt。
想让线程快速、安全、可靠的停止下来并不容易,Java 没有一种机制安全正确的停止线程。
Java 提供了 interrupt ,一种合作机制(使用一个线程通知另外一个线程,让它停止工作)。
被通知要 interrupt 的线程自身拥有最终决定权:被 interrupt 线程本身更加了解自己的业务状态,而不是通知 interrupt 线程。
使用 interrupt 的优点
- 被中断的线程自身拥有如何响应中断的权利(有些线程的某些代码必须具备原子性,必须要等待这些写成处理完成之后,或是准备好之后),再由它们自己主动终止。
- 被中断线程可以完全不理会中断(如果有需要的话),不应该鲁莽的使用 stop()方法,而是 interrupt()
- 需要遵循良好编码规范:让被告知中断的线程自身可以响应中断。
响应中断的方法列表
以下方法可以响应 interrupt,响应的方式为抛出 InterruptedException
* Object.wait()
* Thread.sleep()
* Thread.join
* java.util.concurrent.BlockingQueue.take() /put()
* java.util.concurrent.locks.Lock.lockInterruptibly()
* java.util.concurrent.CountDownLatch.await()
* java.util.concurrent.CyclicBarrier.await()
* java.util.concurrent.Exchanger.exchange(V)
* java.nio.channels.InterruptibleChannel相关方法
* java.nio.channels.Selector的相关方法
最佳实践:普通情况下如何停止线程
package com.xdclass.couponapp.test.stopThreads;
import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl;
/**
* run() 内没有 sleep() 或 wait() 时,停止线程
*/
public class RightWayStopThreadWithoutSleep implements Runnable {
@Override
public void run() {
int num = 0;
//添加线程可以响应中断的判断逻辑,否则线程不会理会interrupt,直到循环结束。
while ( !Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE /2){
if(num % 10000 == 0){
System.out.println(num + "是10000的倍数.");
}
num++;
}
System.out.println("任务运行结束了.");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
613930000是10000的倍数.
613940000是10000的倍数.
任务运行结束了.
最佳实践:线程被阻塞情况下停止线程
当线程处于休眠的过程中,如果接收到 interrupt 信号,响应 interrupt 的方式是抛出 java.lang.InterruptedException: sleep interrupted 异常。
中断线程的写法:catch() 捕获异常,响应 interrupt 信号。
package com.xdclass.couponapp.test.stopThreads;
/**
* 带有 sleep() 的中断线程的写法
*/
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
//匿名内部类(基于接口)
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 300 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
//启动线程
Thread thread = new Thread(runnable);
thread.start();
//在线程处于休眠状态时:发起 interrupt
Thread.sleep(500);
thread.interrupt();
}
}
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18)
at java.lang.Thread.run(Thread.java:748)
最佳实践:如果线程在每次迭代后都阻塞
package com.xdclass.couponapp.test.stopThreads;
/**
* 如果在执行过程中,每次循环都会 sleep() 或 wait() ...
* 那么不需要每次都迭代都检查 Thread.currentThread().isInterrupted()
* 因为 sleep() 过程中的线程接收到 interrupt 消息会抛出异常,因此无需检查
* 只需捕获处理即可
*/
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
//匿名内部类(基于接口)
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 10000) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
//启动线程
Thread thread = new Thread(runnable);
thread.start();
//在线程处于休眠状态时:发起 interrupt
Thread.sleep(5000);
thread.interrupt();
}
}
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
try/catch 在 while 中会导致中断失效
不要在 while 循环中 使用 try/catch 处理 sleep() 对 interrupt 的响应,中断的效果会失效。
* 问什么会一直循环?
答:interrupt 被捕获在循环内部,并没有出错,因此循环会继续。
* 为什么 Thread.currentThread().isInterrupted() 判断不生效?
答:sleep() 响应 interrupt 时候除了抛出异常,还会一并清除线程的 isInterrupted 标记位。
package com.xdclass.couponapp.test.stopThreads;
/**
* 如果 while 循环中使用 try/catch 响应中断,则会导致中断失效。
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数.");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//启动线程
Thread thread = new Thread(runnable);
thread.start();
//在线程处于休眠状态时:发起 interrupt
Thread.sleep(5000);
thread.interrupt();
}
}
0是100的倍数.
100是100的倍数.
200是100的倍数.
300是100的倍数.
400是100的倍数.
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xdclass.couponapp.test.stopThreads.CantInterrupt.lambda$main$0(CantInterrupt.java:16)
at java.lang.Thread.run(Thread.java:748)
500是100的倍数.
600是100的倍数.
实际开发中的两种最佳实践
优先选择:传递中断 (方法的签名上抛出异常)
package com.xdclass.couponapp.test.stopThreads;
/**
* 最佳实践 : catch 了 InterruptedException 之后优先选择:在方法签名中抛出异常。
* 那么在 run() 就会强制 try/catch
*/
public class RightWayStopThreadInProd implements Runnable{
@Override
public void run() {
//try嵌套在while循环外面的意义是:中断while循环(否则只会抛出异常,while会继续循环)
try {
while (true) {
System.out.println("go");
throwInMethod();
}
} catch (InterruptedException e) {
//保存日志 ....
e.printStackTrace();
}
}
/**
* 如果此方法是为其他线程做调用,一定要选择抛出异常,而不是在内部做异常处理
* 这样可以防止本方法吞噬 interrupt
* 应该把处理 interrupt 的方式选择器 交给上层的调用者
* @throws InterruptedException
*/
private void throwInMethod() throws InterruptedException {
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
不想或无法传递:恢复中断
package com.xdclass.couponapp.test.stopThreads;
/**
* 最佳实践2 : 在 catch 子句中调用Thread.currentThread().interrupt() 来回复设置中断状态
* 以便在后续的执行中,让调用者能够检查到刚才发生的中断。
*/
public class RightWayStopThreadInProd2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("go");
reInterrupt();
if (Thread.currentThread().isInterrupted()){
//记录日志
System.out.println("reInterrupt 方法运行期间发生中断!运行结束");
break;
}
System.out.println("go 2");
}
}
/**
* 虽然 catch 住了 interrrupt 引发的异常
* 但是没有独吞,而且是重新恢复了 interrupt
* @throws InterruptedException
*/
private void reInterrupt() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//重新设置 interrupt
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
go
go 2
go
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.reInterrupt(RightWayStopThreadInProd2.java:29)
at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.run(RightWayStopThreadInProd2.java:12)
at java.lang.Thread.run(Thread.java:748)
reInterrupt 方法运行期间发生中断!运行结束
不应该屏蔽中断
独吞中断,并且不恢复中断。
停止线程相关重要函数解析
interrupt() 方法为什么可以中断处于 wait 状态的线程?
追踪源码直至 private native void interrupt0(); (本地方法)
进入 github(也可以进 OpenJDK 网站)
* 设置 interrupted 状态为 true
* _SleepEvent 对应 Thread.sleep()
…… 看不懂
判断是否已经被中断的相关方法
# 用于判断当前线程中断状态的同时,清除线程中断状态
static boolean interrupted():返回当前线程是否已经被中断状态,如果是则返回 true,之后会把线程的中断状态设置为 false。
# 用于判断线程中断状态
boolean isInterrupted():返回当前线程是否已经被中断。
package com.xdclass.couponapp.test.volatiledemo;
/**
* 描述:注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象
*/
public class RightWayInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
}
}
});
// 启动线程
threadOne.start();
//设置中断标志 (threadOne线程被设置为中断)
threadOne.interrupt();
//获取中断标志
System.out.println("isInterrupted: " + threadOne.isInterrupted()); //true (threadOne线程)
//获取中断标志并重置
System.out.println("isInterrupted: " + threadOne.interrupted()); //false 静态方法(main线程)
//获取中断标志并重直
System.out.println("isInterrupted: " + Thread.interrupted()); //false 静态方法(main线程)
//获取中断标志
System.out.println("isInterrupted: " + threadOne.isInterrupted());//true (threadOne线程)
threadOne.join();
System.out.println("Main thread is over.");
}
}
如何处理不可中断阻塞
没有银弹!!!根据特定情况使用特定的方法。
常见错误观点
线程池创建线程也算一种新建线程的方式
* 并不是构建线程的本质
* 查看源码发现线程池也是通过 new Thread(Runnable r) 和 new Thread() 。
package com.xdclass.couponapp.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThradPool5 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.submit(new Task());
}
}
}
class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
pool-1-thread-977
pool-1-thread-980
pool-1-thread-1000
通过 Callable 和 FutureTask 创建线程,也算是一种新的创建线程的方式
* 并不是构建线程的本质
* 查看源码发现 FutureTask 实现了 RunnableFutrue, RunnableFutrue 同时继承了 Runnable 、Future 两个接口。
* 所以本质是离不开 Runnable 接口的。
无返回值是实现 Runnable 接口,有返回值是实现 Callable 接口,所以 Callable 是新的实现线程的方式
* 并不是构建线程的本质
* 同上
定时器也是一种创建线程的方式
package com.xdclass.couponapp.test;
import java.util.Timer;
import java.util.TimerTask;
public class DemoTimerTask {
public static void main(String[] args) {
Timer timer = new Timer();
//每个一秒钟打印一次
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},1000,1000);
}
}
Timer-0
Timer-0
匿名你内部类、Lambda 表达式也是一种创建线程的方式
* 很明显本质还是通过构建Thread,只是语法层面的蜜糖。
package com.xdclass.couponapp.test;
public class AnonymousInnerClassDemo {
public static void main(String[] args) {
//匿名内部类
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());//Thread-0
}
}.start();
//匿名内部类(基于接口)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());//Thread-1
}
}).start();
//Lambda
new Thread(
() -> System.out.println(Thread.currentThread().getName())
).start();//Thread-2
}
}
如何正确的停止线程?
- stop、suspend、resume 方法
* stop 野蛮、它本质上不是安全的。停止线程会导它解锁已所以定的所有监视器。
* suspend、resueme已经废弃
stop 示例
package com.xdclass.couponapp.test.stopThreads;
/**
* 错误的停止线程的方法: 用 stop() 来停止线程,会导致线程运行一半突然停止
* 没办法完成一个基本单元的操作(一个连队),会造成脏数据(有的连队多领、有的连队少领)
* 领取弹药
* 其中有一个连队 居然有些人没有领取到(连队本身丢失了原子性)
* 1连队0号士兵
* 1连队1号士兵
* 1连队2号士兵
* 1连队3号士兵
* 1连队4号士兵
* 1连队5号士兵
* 1连队6号士兵
*/
public class StopThread implements Runnable{
@Override
public void run() {
//一共5个连队,每队10人,以连队为单位,方法武器弹药,叫到号的士兵前去领取。
for (int i = 0; i < 5; i++) {
System.out.println("连队" + i + "开始领取武器");
for (int j = 0; j < 10; j++) {
System.out.println(i + "连队" + j + "号士兵");
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "连队完成领取工作");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new StopThread());
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
}
}
- 用 volatile 设置 boolean 标记位
volatile 示例(看上去似乎是可行的)
package com.xdclass.couponapp.test.volatiledemo;
import sun.tools.jconsole.Worker;
/**
* 演示用 volatile 的局限。
*
* 0是100的倍数
* 100是100的倍数
* 200是100的倍数
* 300是100的倍数
* 400是100的倍数
*/
public class WrongWayVolatile implements Runnable{
//让变量具有可见性
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatile r = new WrongWayVolatile();
Thread thread = new Thread(r);
thread.start();
Thread.sleep(5000);
r.canceled = true;
}
}
volatile 示例(事实证明有局限性,无法在想要停止的时候停止线程)
当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
以下示例发现无法终止生产者,生产者一直在运行,阻塞在队列上。
package com.xdclass.couponapp.test.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
/**
* 当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
* 生产者的生产速度快,消费者消费速度慢
* 阻塞队列满以后,生产者会阻塞,等待消费者进一步消费
*/
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
//队列特点:满的时候放不进去,空的时候取会阻塞。
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
//生产者
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
//消费者
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "已经被消费");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了");
//一旦消费不需要更多数据了,就让生产者停止,但是实际情况
producer.canceled = true;
System.out.println(producer.canceled);
}
}
class Producer implements Runnable {
//让变量具有可见性
public volatile boolean canceled = false;
//队列
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
//这里如果队列已经满,会导致线程一直处于阻塞状态
//队列被消费(少于10个)才会唤醒生产者线程
//如果队列一直没有被消费,那么永远不会执行到while循环的判断条件 !canceled
//所以就算是canceled已经被设置为 true,被阻塞在队列的生产者线程也无法被唤醒
//将一直处于阻塞状态
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓中了");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生者停止运行");
}
}
}
class Consumer {
//队列
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums(){
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
用 interrupt() 代替 volatile 示例(还是 interrupt() 靠谱,修复版)
package com.xdclass.couponapp.test.volatiledemo;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 描述: 用中断来修复刚才的无尽等待问题
* 想要实例化内部类,需要先要实例化外部类,这里仅仅是复习知识而已,仅此而已。
* 为什么要在抛出InterruptedException的时候清除掉中断状态呢?
* 这个问题没有找到官方的解释,估计只有Java设计者们才能回答了。
* 但这里的解释似乎比较合理:一个中断应该只被处理一次(你catch了这个InterruptedException,
* 说明你能处理这个异常,你不希望上层调用者看到这个中断)。
*/
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileFixed body = new WrongWayVolatileFixed();
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = body.new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = body.new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
producerThread.interrupt();
System.out.println(producerThread.isInterrupted()); //true
}
class Producer implements Runnable {
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
/**
* 事实证明:除了 sleep() 其他被阻塞线程在响应 interrupt 的时候 也会清空 isInterrupted 标志位
* 所以这里 使用 isInterrupted() 作为判断条件是毫无意义的
* 需要借助 try.catch 套在 while 外面来中断 while 循环
*/
@Override
public void run() {
int num = 0;
try {
//以下写法毫无意义:因为阻塞线程响应中断的时候会重置 isInterrupted 的值
//while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
while (num <= 100000) {
if (num % 100 == 0) {
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了。");
System.out.println(Thread.currentThread().isInterrupted());
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().isInterrupted());
} finally {
System.out.println("生产者结束运行");
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
}
4900被消费了
5900是100的倍数,被放到仓库中了。
false
5000被消费了
6000是100的倍数,被放到仓库中了。
false
消费者不需要更多数据了。
true
false
生产者结束运行
false
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
at com.xdclass.couponapp.test.volatiledemo.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:56)
at java.lang.Thread.run(Thread.java:748)