停止线程原则
java没有提供任何机制来安全的中止线程,但是提供了中断(Intertuption)这种协作机制,使用中断interrupt通知需要停止的线程,被终止线程自身拥有决定权(决定是否以及何时停止)。被停止线程本身更清楚在何时停止更为合适,外部强行停止可能使数据处于不一致的情况,使用中断协作方式,需要停止时首先清楚当前正在执行的任务,然后再结束。
实践
1、停止普通线程
被停止线程需要监测线程中断状态
public static void main(String[] args) {
Thread thread = new Thread(() ->{
int num = 0;
while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2){
if(num % 10000 == 0){
System.out.println(num + " 为10000的倍数");
}
num ++;
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
2、停止阻塞线程
2.1 停止普通阻塞线程
/**
* @Author: zhangjx
* @Date: 2020/1/14 16:27
* 停止sleep线程
*/
public class RightWayStopThreadWithSleep {
public static void main(String[] args) {
Thread thread = new Thread(() ->{
int num = 0;
while (!Thread.currentThread().isInterrupted() && num <= 300){
if(num % 100 == 0){
System.out.println(num + " 为100的倍数");
}
num ++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
2.1 循环阻塞响应中断
/**
* @Author: zhangjx
* @Date: 2020/1/14 16:34
* 执行过程中每次循环都会阻塞(sleep或wait) 则不需要每次循环都检测中断,sleep中会检测中断
*/
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) {
Thread thread = new Thread(() ->{
int num = 0;
try{
while ( num <= 10000){
if(num % 100 == 0){
System.out.println(num + " 为100的倍数");
}
num ++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
2.3 while中捕获中断
sleep时已经响应过一次中断,中断标记位被清除,无法判定被中断
public static void main(String[] args) {
Thread thread = new Thread(() ->{
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) {
System.out.println("线程被中断");
}
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
解决方案:
- 方法1 : while中不要try catch InterruptedException异常,try catch 直接包裹整个while
- 方法2 : while中try catch InterruptedException异常,使用break,跳出while
- 方法3:传递中断,如果调用其他方法,优先选择在方法上抛出异常,不要使用try catch 捕获异常,以便异常能传到顶层,让run方法可以捕捉这一异常,不要让异常被吃掉。
public static void main(String[] args) {
Thread thread = new Thread(() ->{
int num = 0;
while ( true){
try {
throwInMeyhod();
} catch (InterruptedException e) {
System.out.println("线程中断");
break;
}
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
private static void throwInMethod() throws InterruptedException {
Thread.sleep(1000);
}
- 方法4:恢复中断 catch中捕获处理,重新打上中断标记
public static void main(String[] args) {
Thread thread = new Thread(() ->{
int num = 0;
while ( true ){
if(!Thread.currentThread().isInterrupted()){
System.out.println("线程被中断");
break;
}
System.out.println("hello");
throwInMeyhod();
}
System.out.println("线程运行完毕");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
private static void throwInMeyhod() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
2.4 响应中断的方法总结列表
2.5 错误停止线程的方法
1、被弃用的stop,suspends和resume
stop本质不够安全
suspends和resume 容易死锁
2、volatile关键字设置标记位
部分情况能够实现线程停止,但是如果在线程被阻塞的情况下无法实现
- 无法停止阻塞时的中断,而interrupt设计时已经将长期阻塞作为特殊情况
package com.imooc.thread_demo.stopthreads.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* @Author: zhangjx
* @Date: 2020/8/9 15:11
* @Description: 陷入阻塞中,volatile无法停止线程
*/
public class WrongVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
BlockingQueue queue = new ArrayBlockingQueue(10);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread producerThread = new Thread(producer);
producerThread.start();
/**
* 1、当主线程睡眠1000毫秒时 此时阻塞队列满,生产者被阻塞在queue.put(num)处,此时volatile无法停止线程
* 2、当主线程睡眠500毫秒时 生产者未被阻塞 到while处判断stop为true则停止
*/
Thread.sleep(500);
while(consumer.needProducer()){
System.out.println("数据 " + consumer.queue.take() + "已被消费!");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据!");
producer.stop = true;
}
}
class Producer implements Runnable{
public volatile boolean stop = false;
private BlockingQueue queue;
@Override
public void run() {
int num = 0;
try{
while (!stop && num <= Integer.MAX_VALUE / 2){
if(num % 100 == 0){
queue.put(num);
System.out.println(num + " 为100的倍数");
}
Thread.sleep(1);
num ++;
}
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("生产者运行完毕!");
}
}
public Producer(BlockingQueue queue) {
this.queue = queue;
}
}
class Consumer{
public BlockingQueue queue;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
boolean needProducer(){
return false;
}
}
3、intertupt相关方法辨析
- static boolean interrupted()
检测当前线程是否中断,并清除中断位,该方法关心的是执行该方法的线程
boolean isInterrupted()
检测线程Thread对象是否中断,不清除中断位