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

背景

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(),你能说清楚么?

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值