一、线程中断机制

1.1 中断机制基本概念

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止
所以在Thread中的以下方法已经被废弃

  • 停止多线程: public void stop()
  • 销毁多线程: public void destory()
  • 挂起线程: public final void suspend()
  • 恢复挂起的线程执行: public final void resume()

在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作
因此,Java提供了一种用于停止线程的机制——中断
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,
此时究竟该做什么需要你自己写代码实现。

每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

1.2 API详解

1.2.1 void interrupt()

interrupt()方法用于中断一个线程,而Java中线程的处理是协作式的而不是抢占式的。所谓协作式的意思是:实际上调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定
image.png

1.2.2 static boolean interrupted()

判断线程是否被中断,并清除当前中断状态
这个方法做了两件事:

  • 返回当前线程的中断状态
  • 将当前线程的中断状态设为false

这个方法有点不好理解,因为连续调用两次的结果可能不一样。
image.png

1.2.3 boolean isInterrupted()

判断当前线程是否被中断(通过检查中断标志位)
image.png

1.3 如何使用中断机制

1.3.1 实现中断的大致思路

在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑

1.3.2 实现中断的方式

  • 通过volatile关键字实现
  • 通过AtomicBoolean实现
  • 通过上面介绍的Thread中断API实现

使用interrupt相关API

  1. public static void m3() {
  2. Thread t1 = new Thread(() -> {
  3. while (true) {
  4. if (Thread.currentThread().isInterrupted()) {
  5. System.out.println("-----isInterrupted() = true,程序结束。");
  6. break;
  7. }
  8. System.out.println("------hello Interrupt");
  9. }
  10. }, "t1");
  11. t1.start();
  12. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
  13. new Thread(() -> {
  14. t1.interrupt();//修改t1线程的中断标志位为true
  15. },"t2").start();
  16. }

1.3.3 当前线程的中断标识为true,是不是就立刻停止?

Java中断响应是描述当一个线程或方法A处于运行、阻塞或死锁状态时,外界(通常指其他线程、系统IO等)对A的影响能否让A线程或者方法抛出InterruptedException异常并提前返回,如果会提前返回并且抛出InterruptedException,就叫可中断响应方法或线程,如果不会抛出InterruptedException,就叫不可中断线程或方法

Java中断响应是描述当一个线程或方法A处于运行、阻塞或死锁状态时,外界(通常指其他线程、系统IO等)对A的影响能否让A线程或者方法抛出InterruptedException异常并提前返回,如果会提前返回并且抛出InterruptedException,就叫可中断响应方法或线程,如果不会抛出InterruptedException,就叫不可中断线程或方法

具体来说,当对一个线程,调用 interrupt() 时:

  • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
  • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常

无法打断案例

  1. public static void m5() {
  2. Thread t1 = new Thread(() -> {
  3. while (true) {
  4. if (Thread.currentThread().isInterrupted()) {
  5. System.out.println("-----isInterrupted() = true,程序结束。");
  6. break;
  7. }
  8. try {
  9. Thread.sleep(500);
  10. } catch (InterruptedException e) {
  11. Thread.currentThread().interrupt();//??????? //线程的中断标志位为false,无法停下,需要再次掉interrupt()设置true
  12. e.printStackTrace();
  13. }
  14. System.out.println("------hello Interrupt");
  15. }
  16. }, "t1");
  17. t1.start();
  18. try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
  19. new Thread(() -> {
  20. t1.interrupt();//修改t1线程的中断标志位为true
  21. },"t2").start();
  22. }

中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断

1.4 总结

线程中断相关的方法:

  • interrupt()方法是一个实例方法:它通知目标线程中断,也就是设置目标线程的中断标志位为true,中断标志位表示当前线程已经被中断了。
  • isInterrupted()方法也是一个实例方法:它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
  • Thread类的静态方法interrupted():返回当前线程的中断状态(boolean类型)且将当前线程的中断状态设为false,此方法调用之后会清除当前线程的中断标志位的状态(将中断标志置为false了),返回当前值并清零置false

二、LockSupport中的park等待和unpark唤醒

2.1 简介

我们可以通过使用park()和unpark(thread)方法来实现阻塞和唤醒线程的操作

官方描述:

Basic thread blocking primitives for creating locks and other synchronization classes. This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.)

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit)
permit只有两个值1和零,默认是零,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1

2.2 主要API

2.2.1 park() 实现阻塞

主要作用:阻塞当前线程/阻塞传入的具体线程
image.png

除非许可可用,否则禁用当前线程以进行线程调度。 如果许可可用,则使用它并立即返回调用;否则,当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一:

  • 其他一些线程以当前线程为目标调用 unpark;
  • 或者其他一些线程中断当前线程;
  • 或虚假(即无缘无故)的调用返回。

此方法不报告哪些导致该方法返回。调用者应该重新检查导致线程首先停止的条件。例如,调用者还可以确定线程在返回时的中断状态。

permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。

2.2.2 unpark() 实现唤醒

主要作用:唤醒处于阻塞状态的指定线程
image.png

使给定线程的许可证可用(如果它尚不可用)。如果线程在 park 上被阻塞,那么它将解除阻塞。否则,它的下一次停车呼叫保证不会阻塞。如果给定线程尚未启动,则不保证此操作有任何效果。参数:thread - 取消驻留的线程,或为空,在这种情况下,此操作无效

注意:每个线程只可给指定线程发一张“通行证”

2.3 案例

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo3 {
    public static void main(String[] args) {
        //正常使用+不需要锁块
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒");
        },"t1");
        t1.start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName()+"   -----LockSupport.unparrk() invoked over");
    }
}

之前错误的先唤醒后等待,LockSupport照样支持

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class T1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
                    System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis());
                    LockSupport.park();
                    System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---被叫醒");
        },"t1");
        t1.start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over");
    }
}

解释:
image.png