多线程八大核心知识体系笔记-3.线程的中断

思维导图链接

多线程体系学习笔记链接

参考慕课网Java并发核心知识体系精讲课程

线程的中断 总览

在这里插入图片描述

常见面试题:

1. 如何停止线程
解答:
  • 一、使用interrupt方法请求,而不是强制终止线程,是合作机制

    这样,被请求中断的线程可以自主决定,处理自己的逻辑。

    这样做的好处是,可以保证数据安全,来得及清理,能够保证数据完整性。

  • 二、若让interrupt方法起效,需要多方面的配合使用

    1. 请求方发出中断请求

    2. 被停止方要在每次循环中或适当的时候检查中断信号,

      并在可能抛出InterruptException的时候处理这个信号

    3. 是如果线程停止的是子方法,子方法被调用,有两种最佳实践

      1)传递中断、即优先在子方法层向上抛出异常,将中断信号传给run方法,在run方法层处理中断信号逻辑

      2)恢复中断、即子方法收到中断信号后,再次设置中断状态。

  • 三、 如果不用interrupt,其他方法会有一定的弊端与后果

    1)stop会突然停止线程,线程来不及处理剩下的数据,会导致数据不完整

    2)suspend等方法会使线程挂起,不会破坏对象,抱着锁阻塞,会导致死锁

    3)用volatile设置boolean标记位无法处理长时间阻塞的情况,导致线程无法停止

相关代码证明
子方法中断两种最佳实践:
  • 传递中断:
package threadcoreknowledge.stopthreads;

import threadcoreknowledge.createthreads.ThreadStyle;

/**
 * 描述:     最佳实践1:
 * catch了InterruptedExcetion之后的优先选择:
 * 在方法签名中抛出异常 那么在run()就会强制try/catch
 */
public class RightWayStopThreadInProd implements Runnable {
    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) { // run方法处理异常逻辑
                Thread.currentThread().interrupt();
                //保存日志、停止程序
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    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 threadcoreknowledge.stopthreads;

/**
 * 描述:最佳实践2:
 * 在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,
 * 以便于在后续的执行中,依然能够检查到刚才发生了中断
 * 回到刚才RightWayStopThreadInProd补上中断,让它跳出
 */
public class RightWayStopThreadInProd2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) { // 检测中断
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000); // 中断标志清空,变成false
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 重新中断,标志位设置true
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
用volatile设置boolean标记位不能处理长时间阻塞的情况
package threadcoreknowledge.stopthreads.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 描述:     演示用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); // true
    }
}
// 生产者逻辑
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;
    }
}
  • 修复方案
...
     // 用interrupt来中断
        producerThread.interrupt();
    }

    class Producer implements Runnable {
        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); // 会响应Interrupt中断信号
                        System.out.println(num + "是100的倍数,被放到仓库中了。");
                    }
                    num++;
                }
 ...
2. 如何处理不可中断的阻塞
解答:
    1. 很遗憾,并没有一个通用的解决方案,
    2. 要针对某一些锁、或某一些IO给出不同的解决方案
    3. 比如说,对于ReentrantLock(可重入锁),如果使用Lock方法,并在lock过程中阻塞,没有办法响应interrupt中断响应的,但这个类提供了lockInterrupt这个方法,这个方法可以响应中断的
    4. 即针对特点情况,使用特点方法,争取能够使得它们能够响应中断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值