当前位置: 首页 > news >正文

基础 | 并发编程 - [LockSupport]

INDEX

      • §1 是什么
      • §2 为什么出现
      • §3 使用
      • §4 理解 `par()` 和 `unpark()` 的 许可证(permit)

§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();

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

相关文章:

  • Uboot命令应用
  • kettle-实现不同数据库之间的数据交换
  • OPPO小布4.0:软件定义硬件,智能定义“助手”
  • python获取模块文件路径
  • java计算机毕业设计企业人事管理系统源码+数据库+系统+lw文档+mybatis+运行部署
  • 机器学习算法1---KNN
  • Java--Spring事务
  • 卷妹带你回顾Java基础每日更新Day12
  • UM5006-RT-Thread ART-Pi 数据 flash 擦写手册
  • 【Halcon知识】外轮廓线的算子
  • 能安装Chrome扩展和油猴脚本的手机浏览器
  • 使用Android studio开发一个数独游戏APP 系列第一讲
  • 如何在深度学习中使用自动混合精度训练
  • Linux命令lsscsi详解
  • asp.net webapi 结合Autofac实现程序集动态注入(framework版本)
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 【刷算法】从上往下打印二叉树
  • eclipse(luna)创建web工程
  • ES6--对象的扩展
  • Git同步原始仓库到Fork仓库中
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • select2 取值 遍历 设置默认值
  • Spring框架之我见(三)——IOC、AOP
  • swift基础之_对象 实例方法 对象方法。
  • vue中实现单选
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 如何在 Tornado 中实现 Middleware
  • 使用 Docker 部署 Spring Boot项目
  • 延迟脚本的方式
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • kubernetes资源对象--ingress
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # Apache SeaTunnel 究竟是什么?
  • # 飞书APP集成平台-数字化落地
  • #前后端分离# 头条发布系统
  • (C语言)共用体union的用法举例
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)ssm高校实验室 毕业设计 800008
  • (利用IDEA+Maven)定制属于自己的jar包
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (已解决)什么是vue导航守卫
  • (转)Unity3DUnity3D在android下调试
  • (轉貼) UML中文FAQ (OO) (UML)
  • *上位机的定义
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core 通过 Ef Core 操作 Mysql
  • .net中调用windows performance记录性能信息
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • @hook扩展分析
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [Android Studio] 开发Java 程序
  • [AX]AX2012 SSRS报表Drill through action