LockSupport.park()被唤醒——unpark()与interrupt()有何不同

    科技2024-12-26  5

    背景

    ReentrantLock 在阻塞线程,用的是LockSupport.park(),(ReentrantLock源码解析)

    与这对应,唤醒线程,调用LockSupport.unpark()。

    可看源码时,会发现,调用LockSupport.park(),紧接着会调用Thread.interrupted(),Why?

    private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // 阻塞线程 return Thread.interrupted(); // 清除线程中断标记 }

    这里,以此为背景,聊聊LockSupport.park(),怎样可被唤醒,

    顺便演示下 isInterrupted(), interrupt(), interrupted()

    结论

    直接说结论:

    LockSupport.park(),可通过两种方式被唤醒,LockSupport.unpark() 或者 interrupt()public void interrupt(){} 给线程打一个中断标志public boolean isInterrupted(){} 检测下线程是否被中断,不清除中断标志public static boolean interrupted() {} 也是检测下线程是否被中断,但清除中断标志

    Demo 测试

    1、unpark()测试

    public static void main(String[] args) { Thread t0 = new Thread(new Runnable() { @Override public void run() { Thread current = Thread.currentThread(); // LockSupport.unpark(current); log.info("current Thread name:{}",current.getName()); for(int i = 0; i < 3; i++){ log.info("for循环第{}次,begin",i+1); log.info("准备park当前线程:{}",current.getName()); LockSupport.park(); log.info("线程{}阻断后又运行了",current.getName()); log.info("for循环第{}次,end",i+1); } } },"t0"); t0.start(); try { log.info("休眠…………"); Thread.sleep(2000); log.info("调用LockSupport.unpark方法,唤醒线程{}", t0.getName()); LockSupport.unpark(t0); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }

    t0 线程运行时,第一次循环,调用park()后线程就阻断了,LockSupport.unpark(t0)执行后,阻断解除,会进行第二次循环,再次被阻断,就停着不动了。

    LockSupport.unpark() 比较好玩的是,可以放在 park() 前面。

    这是把第七行,那个注释掉的代码放开之后,打印的结果,循环会在第3次运行时阻塞

    放在线程内部的那个unpark(),把第一次循环中的内个park()给唤醒了。

    第二次循环中的 park(),被线程外的那个unpark() 唤醒了。 那有人可能会问:如果在线程内连续调用两次 unpark(),是不是第3次循环里的park() 也会被唤醒呀!

    答案是不会,unpark() 相当于一个凭证,连续调用两次,还是只有这一个凭证,

    当调用 park()时,会检查有没有这个凭证,没有就阻塞,有就不阻塞,并且会消费掉这个凭证。

    2、interrupt() 测试 看下结果,真的不一样。for 循环成功运行了3次,线程结束运行了。 可明明调用了三次 park(),即调用了三次阻塞呀!

    只要线程有中断标志,park() 就不再阻塞线程,不论调用多少次park() ,都不会阻塞线程。

    我C++语言学的不好,没能力剖析源码。

    有能力的,可以看下C++中 LockSupport.park()的源码,若有中断标志,park就不阻塞线程。

    3、Thread.interrupted() 测试 从上个例子的图片中,可以看出 调用

    t0.interrupt() 之前,t0.isInterrupted()返回false

    而调用之后,t0.isInterrupted()返回 true

    下面比较下 Thread.interrupted() 和 isInterrupted() 源码

    public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); } // 这两个方法都是调用的下面这个方法,传入true 清除中断标志,false 不清除 /** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. * 检测当前线程是否被中断。其中断状态是否被重置,取决于传入参数 */ private native boolean isInterrupted(boolean ClearInterrupted);

    看完源码,改造下Demo,改动如下

    log.info("调用t0.interrupt,唤醒线程{} t0.isInterrupted()={}", t0.getName(),t0.isInterrupted()); t0.interrupt(); log.info("调用Thread.interrupted()前,t0.isInterrupted()={}", t0.isInterrupted()); boolean interrupted = Thread.interrupted(); log.info("Thread.interrupted()返回结果:{}, t0.isInterrupted()={}", interrupted, t0.isInterrupted());

    在这里想下,调用了Thread.interrupted() 返回的结果应该是 true,因为前面调用 interrupt()。

    同样,t0.isInterrupted() 应该返回 false, 因为中断状态已被清除了, for 循环也将被阻断。

    分析完了,运行下看结果,是否与预期一致 哦吼,预计完全错了,Thread.interrupted()返回的是false,即未有中断。

    t0.isInterrupted()返回的是true,线程是中断的

    for循环也执行完了。Why?

    不是API 有问题,

    Thread.interrupted() 中断的是当前线程,

    代码中执行 Thread.interrupted() 这行代码的是main线程。

    这是一个大坑,源码写的很清楚 return currentThread().isInterrupted(true); 当前线程,当前线程,当前线程

    Demo改成这个样子, Thread.interrupted()放到For循环中

    public static void main(String[] args) { Thread t0 = new Thread(new Runnable() { @Override public void run() { Thread current = Thread.currentThread(); log.info("current Thread name:{}",current.getName()); for(int i = 0; i < 3; i++){ log.info("for循环第{}次,begin",i+1); log.info("准备park当前线程:{}",current.getName()); LockSupport.park(); boolean first = Thread.interrupted(); boolean second = Thread.interrupted(); log.info("线程{}阻断后又运行了 first={}, second={}",current.getName(),first, second); log.info("for循环第{}次,end",i+1); } } },"t0"); t0.start(); try { log.info("休眠…………"); Thread.sleep(2000); log.info("调用t0.interrupt,唤醒线程{} t0.isInterrupted()={}", t0.getName(),t0.isInterrupted()); t0.interrupt(); log.info("t0.isInterrupted()={}", t0.isInterrupted()); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }

    首先,for循环没进行完,线程被阻断了,说明阻断状态确实被清除了。

    连续再次调用 Thread.interrupted(),即线程是被中断的,返回true,并清除中断标志。 第二次调用Thread.interrupted(),被清除中断标志的线程,就是没有中断,返回false。

    至此,LockSupport.park(),LockSupport.unpark(), isInterrupted(), interrupt(), interrupted() 这几个方法都演示了,

    现在回到开篇的背景问题: ReentrantLock 中 parkAndCheckInterrupt() 为会么会调用Thread.interrupted(),你能说清楚么?

    Processed: 0.515, SQL: 8