首先对于线程的停止有一个原则需要记住:想要停止一个线程,应该是让该线程知道他需要停止【通过协作的方式】,而不是强制停止。
下面就来说一下如何做到通知线程应该要停止了。
使用interrupt停止线程
package ltd.personalstudy.threadbasic;
/**
* @Author 咖啡杯里的茶
* @date 2020/12/6
*/
public class StopThread implements Runnable{
@Override
public void run() {
int count = 0;
// 检查线程的标识位,没有被别的线程中断并且满足业务要求就继续执行
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count=" + count++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt(); // 中断线程
}
}
isInterrupted()不会修改线程中断标识位
sleep过程中能否感知到线程中断
package ltd.personalstudy.threadbasic;
import java.sql.Struct;
/**
* @Author 咖啡杯里的茶
* @date 2020/12/6
*/
public class StopThread implements Runnable{
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count=" + count++);
}
System.out.println(Thread.currentThread().isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
执行结果
- 在sleep、wait等可以让线程进入阻塞的方法使线程休眠了,处于休眠状态的线程被中断后,线程是可以感知到中断信号的,并且会抛出一个InterruptedException,同时会清楚中断信号
最佳处理方式
直接抛出异常
再次中断
错误的线程停止方法
- stop()
- 该方法会直接停止线程,不够友好
- suspend()
- 该方法调用之后线程会挂起,但是在调用resume之前是不会释放锁的,这样容器导致死锁,所以现在也不使用了。
- resume()
上面的三个方法以及被java废弃了,使用这三个方法来停止线程是错误的。
使用volatile修饰标记位
可以使用volatile的情形
如果所示,上面的代码使用volatile修饰的变量来停止线程是可以的,可以的原因就是在while循环里面的代码是没有发生阻塞的,如果在里面的代码发生了阻塞,即使外面修改volatile的标识位,但是由于不会进入下一次循环,所以也是不能停止线程的。
volatile不能正确停止的事例
package ltd.personalstudy.threadbasic;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* @Author 咖啡杯里的茶
* @date 2020/12/6
*/
public class StopThread {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(500);
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 (!canceled && num < 1000000) {
if (num % 50 == 0) {
System.out.println(num + "是50的倍数,被放到仓库中了");
storage.put(num);
}
num++;
Thread.sleep(1);
}
} 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.97) {
return false;
}
return true;
}
}
在上面的代码中生产者和消费者使用了同一个阻塞队列,在生产者的run方法中有这么一段代码
如果代码在这里面阻塞了,那么即使外面修改了canceled变量,也会因为由于阻塞而不能进入下一次while循环导致不能正确的停止线程。