如何停止线程?
A. 用volatile的boolean作为标记来停止
B. 用stop()方法让线程停止
C. 用interrupt来请求线程停止
解答:
应该选C。
原理:用interrupt来请求线程停止而不是强制,好处是安全。
想停止线程,要请求方、被停止方、子方法被调用方相互配合才行:
a) 作为被停止方
:每次循环中或者适时检查中断信号,并且在可能抛出InterrupedException的地方处理该中断信号;
b) 请求方
:发出中断信号;
c) 子方法调用方(被线程调用的方法的作者)要注意
:优先在方法层面抛出InterrupedException,或者检查到中断信号时,再次设置中断状态;
最后再说错误的方法:stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况
错误停止线程之volatile设置boolean标记位
从三步来展开:
1、看似可行(部分情况适用)
2、错误之处
3、修正方案
一、看似可行
package threadcoreknowledge.stopthreads.volatiledemo;
/**
* 描述:演示 volatile的局限性,part1看似可行
*/
public class WrongWayVolatile implements Runnable{
public volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while(num <= 10000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
WrongWayVolatile r = new WrongWayVolatile();
Thread thread = new Thread(r);
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.canceled = true;
}
}
二、错误之处
详述示例代码整体思路:
我们首先有一个生产者Producer
和一个消费者Consumer
,生产者负责生产(代码是循环num++,把是100的倍数加入到一个阻塞队列BlockingQueue),BlockingQueue的容量初始化设置为10,当队列满了之后(就是等于10),生产者会阻塞,也就是Producer的run()方法的while循环中的storage.put(num)会进入wait状态阻塞
,这时必须要等消费者Consumer拿num数据,就是storage.take(num),生产者才能继续往阻塞队列BlockingQueue加num,也就是storage.put(num)这步才会执行走下去。然后就是我们设置生产者Producer的生产速度很快,消费者Consumer消费速度慢Thread.sleep(100);
,目的就是为了让生产者Producer大多数情况处于一个阻塞状态
,最后就是设置一个needMoreNums()方法,方法目的也很简单,就是让消费者在某一时刻说:我不需要更多数据了,就是消费者Consumer不需要数据了,那对应的,这时生产者Producer也就应该停止生产了,也就是我们代码编写者此时应该请求中断生产者Producer的线程,然后我们就通过volatile设置boolean标记位
的方式中断线程,看看会发生什么。。。
package threadcoreknowledge.stopthreads.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 描述: 演示用volatile的局限part2
* 陷入阻塞时,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) {
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;
}
}
/**
* 为什么用volatile停止线程不够全面
*
* 解答:这种做法是错误的,或者说是不够全面的,在某些情况下虽然可用,但是某些情况下有严重问题。
* 这种方法在《Java并发编程实战》中被明确指出了缺陷,我们一起来看看缺陷在哪里:
* 此方法错误的原因在于,如果我们遇到了线程长时间阻塞(这是一种很常见的情况,例如生产者消费者模式中就存在这样的情况),
* 就没办法及时唤醒它,或者永远都无法唤醒该线程,而interrupt设计之初就是把wait等长期阻塞作为一种特殊情况考虑在内了,
* 我们应该用interrupt思维来停止线程。
* */
打印结果
这说明了已经把volatile设置的标记位canceled
设置为true
,但神奇地是Producer的线程并没有被停止,而导致线程没有被停止原因是:
三、修正方案
1、改用interrupt()中断
2、把Producer 和 Consumer改成内部类
(没什么原因,就是感受一下内部类用法)
下面直接贴代码:
package threadcoreknowledge.stopthreads.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
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();//改用interrupt()中断
}
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 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
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;
}
}
}
关于!Thread.currentThread().isInterrupted()
是否多余的问题:
无法响应中断时如何停止线程?(处理不可中断
的阻塞)
A. 用interrupt方法来请求停止线程
B. 不可中断的阻塞无法处理
C. 根据不同的类调用不同的方法
解答:
应该选C。
如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛出 InterruptedException 异常来唤醒该线程。
但是对于不能响应InterruptedException的阻塞,很遗憾,并没有一个通用的解决方案。
但是我们可以利用特定的其它的可以响应中断的方法
,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。
总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药。