基础 | 并发编程 - [LockSupport]

§1 是什么

在这里插入图片描述

  • 属于 JUC
  • 是个类
  • 用于创建锁和其他同步类的基本线程阻塞原语
    是 JDK 对线程阻塞的实现,AQS 与其他锁都是基于它实现的

§2 为什么出现

主要是因为 synchronizedLock 存在限制,无论是 wari() + notify() 还是 await() + signal()

  • 依赖于 java 的 monitor
    当没有 synchronizedlock() 就调用上述方法都会导致抛出 IllegalMonitorStateException
  • 强调顺序
    先进行唤醒后阻塞语法上虽然允许但逻辑中会导致无法退出锁

LockSupport 则可以绕开上述两点,
这是因为 LockSupport 是直接对线程进行阻塞唤醒
synchronizedLock 则是基于线程上挂载的锁对象的 monitor

LockSupport 通过 par()unpark() 方法提供线程的阻塞和唤醒功能
这两个方法在底层依赖于 Unsafepar()unpark() 方法实现,Unsafe 的这两个方法属于原语方法
更具体的细节参考 理解 par()unpark() 的 许可证(permit)

§3 使用

public static void main(String[] args) {
    Thread a = new Thread(()->{
        System.out.println("A");
        LockSupport.park();
        System.out.println("AA");
    });

    Thread b = new Thread(()->{
        System.out.println("B");
        LockSupport.unpark(a);
        System.out.println("BB");
    });

    a.start();
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) { e.printStackTrace(); }
    b.start();
}

§4 理解 par()unpark() 的 许可证(permit)

par()unpark()对线程的效果受 许可证(permit) 影响

  • permit 相当于一块 <免死金牌>
  • 每个线程只有一个 permit,即只有一块 <免死金牌>
  • permit 具有且只具有 两种状态,0、1
    这意味着连续两次发放线程的 permit 后,线程持有的 permit 数量还是 1
    • 0,线程不再持有 permit,相当于被 LockSupport 扣留,或本身就没有
    • 1,线程持有 permit,相当于被 LockSupport 发放
  • par() 会试图将线程从预计可执行的行列中剔除出去,即对线程造成 <阻塞打击>
    • 线程持有 permit,相当于持有 <免死金牌>,则 消耗掉 <免死金牌> 以免疫 此次 打击par() 立即返回,
      这意味着 par() 失败,即线程 不阻塞,同时没有 <免死金牌> 了
    • 否则,相当于没有 <免死金牌>,线程直接承受 <阻塞打击>,于是线程被从预计可执行的行列中剔除
      这意味着 par() 成功,即线程 阻塞
  • unpar() 会增加 permit ,相当于又发放一块 <免死金牌>
    • 若线程本身阻塞,就从阻塞状态中唤醒,但不获取的 <免死金牌> 效果
    • 若线程本身没阻塞,则持有一块 <免死金牌>,免疫下一次 <阻塞打击>

相关说明见下面源码注释
在这里插入图片描述
在这里插入图片描述

这里有两个需要注意的点

  • permit 并不是直接线程的运行状态的变更生效的,这与传统的锁不同
    permit 直接影响的是 par()unpark() 的执行效果
  • permit 有 0,1 两个状态,但对应到线程其实是三个状态
    • 眩晕:blocked
    • 裸奔:unblocked & no-permit
    • 带盾:unblocked & permit

基于上面两点的理解, par()unpark() 的作用其实可以总结为让线程在上面三个状态中切换,结合下表

状态par()unpark()
眩晕无变化_眩晕裸奔
裸奔眩晕带盾
带盾裸奔无变化_带盾
死 / 未运行无变化无变化

示意图
在这里插入图片描述
代码验证

public static void main(String[] args) {
    final boolean[] sword_twice = new boolean[]{false};//对线程 a,是刀 1 次,还是刀 2 次

    // a 中的 sleep 都是 10 毫秒,如果没有阻塞,会很快执行到下一句
    // 如果阻塞了,会出现 b 的信息,然后才是 a 后面的输出
    Thread a = new Thread(()->{
        System.out.println("A");
        LockSupport.park();
        longSound(10,1);
        System.out.println("AA");
        if(sword_twice[0]){
            longSound(10,1); // longSound(100,1) in case 3
            LockSupport.park();
            longSound(10,1);
            System.out.println("AAA");
        }
    },"a");

    // b 中的 sleep 都是 3000 毫秒,如果有阻塞,会有很明显的 b 的信息,然后才轮到 a 的信息
    Thread b = new Thread(()->{
        System.out.println("B");
        longSound(1000,3);
        LockSupport.unpark(a);
        System.out.println("BB");
    },"b");
    
	//cases    
}
private static void longSound(int length,int times) {
    try {
        for(int i=0; i<times; i++) {
            TimeUnit.MILLISECONDS.sleep(length);
            System.out.println("......long......sound......by......"+Thread.currentThread().getName());
        }
    } catch (InterruptedException e) { e.printStackTrace(); }
}

case 1 正常的锁功能

System.out.println("========== case 1 ==============");
a.start();
longSound(100,1);
b.start();

在这里插入图片描述

case 2 (提前给金牌,可以免疫一次 park)

System.out.println("========== case 2 ==============");
sword_twice[0] = true;
a.start();
LockSupport.unpark(a);
longSound(100,1);
b.start();

在这里插入图片描述

case 3 从阻塞状态给两次金牌,可以免疫一次 park,第一次救活线程,第二次免疫
注意按注释调一下睡眠时间,否则不严谨

System.out.println("========== case 3 ==============");
sword_twice[0] = true;
a.start();
longSound(10,1);
//连续执行两次的话会卡在第二次 park,怀疑是紧邻的两次 unpark 其实是同时生效于一个状态,导致第二个失效
//第二次 park 前,会睡 100ms,足够这里的两次 10ms 的 unpark 跑完
LockSupport.unpark(a);
longSound(10,1);
LockSupport.unpark(a);

在这里插入图片描述

case 4 & 5 对比,线程没有启动时,park 和 unpark 是不一定生效的

System.out.println("========== case 4 ==============");
sword_twice[0] = false;
a.start();
LockSupport.unpark(a);
longSound(100,1);
b.start();
System.out.println("========== case 5 ==============");
sword_twice[0] = false;
LockSupport.unpark(a);
a.start();
longSound(100,1);
b.start();

在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值