正确停止的方法 interrupt
通常线程会在什么情况下停止。
在线程运行时如何阻塞线程
public class RightWayStopThreadWithoutSleep implements Runnable {
//正常运行状态下的中断线程法
@Override
public void run() {
int num = 0;
//这里加了 判断 如果外部 执行了中断 会有一个标记位设置true
//这样的话 是通过在线程内部添加 检查来判断的
while(!Thread.currentThread().isInterrupted() &&num <= Integer.MAX_VALUE /2 ){
if(num % 10000 == 0 ){
System.out.println(num + "是100000万的倍数");
}
num ++;
}
System.out.println("任务运行结束了");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
//开启一个线程
thread.start();
//等待2秒后
thread.sleep(2000);
//线程中断请求 我们提出了但断不断 看线程心情
thread.interrupt();
}
}
直接调用线程的 interrupt(); 线程 不会自己停止 原因是线程有权按照自己的实际情况来自行决定什么时候该中断。
所以通过 在线程内部 方法中 手动添加 Thread.currentThread().isInterrupted() 判断当前线程有没有 被设置了中断来处理。
睡眠状态下线程的阻断
package stopThread;
import org.omg.PortableServer.THREAD_POLICY_ID;
public class RightStopThreadwithSleep {
/**
* 带有sleep的中断线程的写法
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Runnable runnable =() ->{
int num = 0;
//以下这句判断 外部是否对线程 中断 其实不需要 原因如 1.
while(num <= 300 && !Thread.currentThread().isInterrupted()){
if(num %100 == 0){
System.out.println(num + "是100的倍速");
}
num ++;
}
try {
//1.休眠1秒 休眠状态下 其实程序会自动响应中断请求
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
//休眠 500毫秒
Thread.sleep(500);
//会报错 睡眠时中断 标志位清空
thread.interrupt();
}
}
在线程运行过后500毫秒 执行中断 但是 此时线程内执行了完成后 会休眠一秒 此时中断 的话 就会导致线程内中断倍打断 并抛出异常:
所以最好在 sleep() 做异常处理。
如果再循环中会有休眠状态 这时中断会自动响应
package stopThread;
import org.omg.PortableServer.THREAD_POLICY_ID;
public class RightStopThreadwithSleep {
/**
* 带有sleep的中断线程的写法
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Runnable runnable =() ->{
int num = 0;
try {
while(num <= 1000000){
//这部分执行耗费很低
if(num %100 == 0){
System.out.println(num + "是100的倍速");
}
num ++;
//整个方法 大部分运行时间消耗在 睡眠中
Thread.sleep(20);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
//休眠 500毫秒
Thread.sleep(1000);
//中断
thread.interrupt();
}
}
在休眠的过程中 不需要在代码中 去判断是否需要响应外部的中断 java会有自己中断的机制自动响应中断。
改变try catch 导致中断无法实现(错误)
package stopThread;
public class CannetInterupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable =() ->{
int num = 0;
//由于 1的原因 导致中断没法响应 在while 判断 加上isInterrupted() 来判断是否被外部中断了
//3. 上面是想的是理想情况 由于 2. 的原因导致 其实标志位 已经被清除了 也就没法判断了 所以加上判断也无济于事
while(num <= 1000000 && !Thread.currentThread().isInterrupted()){
//这部分执行耗费很低
if(num %100 == 0){
System.out.println(num + "是100的倍速");
}
num ++;
//1.改变 catch 位置 导致错误倍catch住了 就没满足while 跳出的条件 就继续执行下一次循环了
try {
Thread.sleep(20);
//2.一旦程序响应了中断 中断标记位就会被清除
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
//休眠 500毫秒
Thread.sleep(1000);
//中断
thread.interrupt();
}
}
由于改变了 try-catch 的位置 而导致无法响应中断。
以上是一些中断的基本方法 有好有坏。但在实际开发中又该怎么中断呢?
- 优先选择:传递中断
- 不想或无法传递: 恢复中断
- 不应屏蔽中断
❌示例
package stopThread;
/**
* DESC:最佳实践 在方法签名中抛出异常
* run() 强制就会 try/catch
*/
public class RightWayStopInProd implements Runnable{
@Override
public void run() {
while(true){
System.out.println("go");
throwInMethod();
}
}
private void throwInMethod() {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
//?这里已经处理了错误 人家帮你处理了 你都还不知道怎么处理好的错误。不太好
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopInProd());
thread.start();
thread.sleep(1000);
thread.interrupt();
}
}
解释下第一点 看上面的实例 程序报错了却还在一直运行 因为在底层 已经处理了错误 上面的方法去调用的时候完全是没法处理的 这样子问题就来了 当你调用别人的写代码库的时候 有些错误 你都没做法做出自己的处理方法 要么你就把他代码修改修改 处理你这个错误 但这样真的好吗 ?
正确抛出异常
package stopThread;
public class RunThrowException implements Runnable {
@Override
public void run() {
while(true && !Thread.currentThread().isInterrupted()){
System.out.println("go");
System.out.println(Thread.currentThread().getName());
try {
throwInMethod();
} catch (InterruptedException e) {
System.out.println("打印异常日志");
}
}
}
private void throwInMethod() throws InterruptedException {
//直接抛出异常
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RunThrowException());
thread.start();
thread.sleep(1000);
thread.interrupt();
}
}
一般异常要抛出到顶层人员处理,因为我们不知道调用的人的业务逻辑所以不能擅自帮助别人就处理了错误。
当我们无法不想抛出异常时
package stopThread;
/**
* DESC:最佳实践 在catch自语句中调用Thread
* currentThread.interrupt()来恢复设置中断状态,一遍于在后续的执行中,依然能够检查到刚才发生的中断
* 回到刚才 RightWayStopInProd2 不上中断 跳出
*/
public class RightWayStopInProd2 implements Runnable{
@Override
public void run() {
while(true ){
//当程序感知到异常退出
if(Thread.currentThread().isInterrupted()){
System.out.println("go");
break;
}
throwInMethod();
}
}
private void throwInMethod() {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
//当中断 被触发 且被catch住 那就 没办法响应了 那怎么办呢
//重新 设置下 刚才被清除的中断
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopInProd2());
thread.start();
thread.sleep(1000);
thread.interrupt();
}
}
当catch住 中断后 中断标记会消失 顶层就没法处理这个错误 解决方法 在把Thread.currentThread().interrupt();中断恢复。
其他停止方法
错误的停止方法 Stop (弃用)
package stopThread;
public class StopThread implements Runnable {
@Override
public void run() {
//模拟订单生成 到出库的一个过程 关键是这个过程是原子性的不可以被打断
for (int i = 0;i<5;i++){
System.out.println("订单" + i +"开始创建");
for (int j = 0;j<4;j++){
if(j == 0)
System.out.println("订单" + i +":订单生成");
else if(j == 1)
System.out.println("订单" + i +":订单扣减库存");
else if (j == 2)
System.out.println("订单" +i +":订单付费成功");
else if (j == 3)
System.out.println("订单" +i +":订单自动分配仓库");
else if (j == 4)
System.out.println("订单" +i +":订单自动出库");
try{
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("订单" + i + ":完成");
}
}
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(new StopThread());
a.start();
Thread.sleep(1000);
a.stop();
}
}
假设有一个方法是 处理 订单生成到发货的 如果使用 stop()去中断 程序会 戛然而止 这样就没法 完整的处理完一个流程了 这是个很严重的问题 如果在处理某些问题的时候 突然被中断 导致订单数据 出现错乱。
暂停线程Suspend (弃用)
容易造成死锁 当他挂起的时候 会带着锁 如果其他线程没有及时把它唤醒 或者唤醒它的线程需要它这把索就会形成死锁。
Volatitle的标记位
使用Volatitle来终止线程
package stopThread.volatiledemo;
/**
* 描述: 演示用volatitle的局限
*/
public class WrowngWayVolatitle implements Runnable {
private 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) throws InterruptedException {
WrowngWayVolatitle wr = new WrowngWayVolatitle();
Thread a = new Thread(wr);
a.start();
a.sleep(5000);
//通过设置标志位 来实现 程序的停止
wr.canceled =true;
}
}
在以下情况 假设有一个生产者 一个消费者 当消费者 在消费时停止线程 这时候就没法用 volatitle
package stopThread.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 演示volatitle的局限
* 陷入阻塞式,volatitle是无法实现的
* 此例中 生产者的生产速度很快 消费者速度慢 所以阻塞队列满了以后 生产者会阻塞 ,等待消费者进一步消费
*/
public class WorngWayVolatitleCannotStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storge = new ArrayBlockingQueue(10);
Producer producer = new Producer(storge);
Thread thread = new Thread(producer);
thread.start();
Thread.sleep(1000);
Consumer c1 = new Consumer(storge);
while(c1.needMoreNums()){
System.out.println(c1.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) {
System.out.println(num + "是100的倍数放到仓库里" );
this.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.95) {
return false;
}
return true;
}
}