开心一刻

  今天突然收到花呗推送的消息,说下个月 9 号需要还款多少钱

  我就纳了闷了,我很长时间没用花呗了,怎么会欠花呗钱?

  后面我一想,儿子这几天玩了我手机,是不是他偷摸用了我的花呗

  于是我找到儿子问了起来

  我:儿子,你是不是用了我的花呗

  儿子:是的呀,爸,我就用了一点

  我:额度就剩两块了,你用了我用什么?

  儿子:你用你爸的呗!

  我:...

  不对呀,我女朋友都没有,哪里的儿子?猛的被惊醒,大白天的,我特么竟然还做上了白日梦!

前言

  本文是基于 JDK1.8

  那么此时 Java 线程与操作系统线程的对应关系是 1:1 的,有兴趣的可以读一读:深入聊聊java线程模型实现?

  至于 Java 是否在未来引入类似 Go 中的协程,从而实现 Java 线程与操作系统线程的关系是 m:n,那是未来的事,那就未来再说

  我们能确定的是:Java8 中,Java 线程与操作系统线程是 1:1 的

LockSupport 简介

  关于 LockSupport,我们对它感到很陌生,因为我们在工作中很少直接接触到它,但多多少少,我们都间接用到过它

  LockSupport 是 JUC 包下很重要的一个工具类,我们来看看它的源码概述:

    Basic thread blocking primitives for creating locks and other synchronization classes

    用于创建锁和其他同步类的基本线程阻塞原语

  JUC 包下的锁、同步类基本都依赖 LockSupport 实现线程的阻塞与唤醒

  我们可以简单的认为 LockSupport 对 Java 线程(操作系统线程)的阻塞与唤醒进行了封装,简化了开发人员的任务

  permit(许可证)

  LockSupport 的设计思路就是为每一个线程设置一个 permit,其实就是一个值,类似于 AQS 中的 state

  但 permit 没有显示的存在于 LockSupport 的源码中,而 state 却显示的存在于 AQS 的源码中( private volatile int state; )

    permit 默认值(初始值)是 0,permit 最小值是 0,最大值是 1;0 表示许可证不可用,1 表示许可证可用

    若 permit 值为 0,则 park 方法会阻塞当前线程,直至超时或有可用的 permit;若 permit 为 1 ,则 park 方法会将 permit 值设置成 0,不会阻塞当前线程

    不管 permit 的值是 0 还是 1,unpark 方法会将 permit 设置成 1,也就说多次 unpark (中间没有 park)后,permit 的值仍是 1

  那么问题来了,permit 不在 LockSupport 中,那么它在哪?

  其实 permit 体现在 JVM 中,我们来看看在 Hotspot 中对应的源码,在 /hotspot/src/share/vm/runtime/park.hpp 中有如下一段

class Parker : public os::PlatformParker {
private:
volatile int _counter ;
Parker * FreeNext ;
JavaThread * AssociatedWith ; // Current association public:
Parker() : PlatformParker() {
_counter = 0 ;
FreeNext = NULL ;
AssociatedWith = NULL ;
}
protected:
~Parker() { ShouldNotReachHere(); }
public:
// For simplicity of interface with Java, all forms of park (indefinite,
// relative, and absolute) are multiplexed into one call.
void park(bool isAbsolute, jlong time);
void unpark(); // Lifecycle operators
static Parker * Allocate (JavaThread * t) ;
static void Release (Parker * e) ;
private:
static Parker * volatile FreeList ;
static volatile int ListLock ; };

  这个 volatile int _counter 就是 permit 的底层具体实现

LockSupport 核心方法

  方法不多,如下图

  

  主要分两类:park 和 unpark ,我们针对这几个方法,一个一个来看,注意多看注释

  park

  会消耗 permit,若当前没有可用的 permit,则会阻塞当前线程

  park()

    方法体非常简单

    简单的一行: UNSAFE.park(false, 0L); 关于 Unsafe,有兴趣的可以去了解下:Java魔法类:Unsafe应用解析

    只看这个代码,我们很难看出什么,所幸有方法注释,简单翻译一下

    1、除非 permit 可用,否则阻塞当前线程直至 permit 可用

    2、如果 permit 可用,会将 permit 设置成 0,立即返回,不会阻塞当前线程

    3、当 permit 不可用时,当前线程会被阻塞,直至发生以下三种情况

      3.1 其他线程调用 unpark 唤醒此线程

      3.2 其他线程通过 Thread#interrupt 中断此线程

      3.3 该调用不合逻辑地(即毫无理由地)返回,可能是操作系统异常导致的

    4、park() 不会报告是什么原因导致的调用返回,有需要的话,调用者需在返回时自行检查是什么条件导致调用返回

  park(Object blocker)

    方法体也很简单

    功能与 park() 一样,只是多了个入参:Object blocker ,在线程被阻止时记录此对象,以允许监视和诊断工具识别线程被阻止的原因

    我们通过 jstack 命令,来看看 park() 和 park(Object blocker) 线程快照信息有什么区别

    示例代码:

    用 park() 时线程 t1 的快照信息如下

    用 park(Object blocker) 时线程 t1 的快照信息如下

    我们发现 park(Object blocker) 多了一行: - parking to wait for <0x000000076bbb5108> (a java.lang.String)

    当然 park(Object blocker) 不会像示例中那么使用(传个固定的字符串),传的肯定是有意义的对象,我们来看看 JDK 中哪些地方用到了它

    感兴趣的可以去看看具体的代码,其中的 this 具体是什么,它作为 blocker 有什么作用

  parkNanos(long nanos)

    nanos 表示等待的最大纳秒数;我们来翻译一下方法的注释

    1、除非 permit 可用,否则阻塞当前线程直至 permit 可用,或者等待的时间结束

    2、如果 permit 可用,会将 permit 设置成 0,立即返回,不会阻塞当前线程

    3、当 permit 不可用时,当前线程会被阻塞,直至发生以下四种情况

      3.1 其他线程调用 unpark 唤醒此线程

      3.2 其他线程通过 Thread#interrupt 中断此线程

      3.3 经过指定的等待时间,不会无限期的等待下去

      3.4 该调用不合逻辑地(即毫无理由地)返回,可能是操作系统异常导致的

    4、park() 不会报告是什么原因导致的调用返回,有需要的话,调用者需在返回时自行检查是什么条件导致调用返回

    可以看出,功能与 park() 基本一致,只是多了一个等待时长

  parkNanos(Object blocker, long nanos)

    功能与 parkNanos(long nanos) 基本一样,只是多了个 Object blocker

    将 parkNanos(Object blocker, long nanos) 与 parkNanos(long nanos)  的关系与 park(Object blocker) 于 park() 的关系进行类比,就好理解了

    JDK 中有很多地方用到了 parkNanos(Object blocker, long nanos)

  parkUntil(long deadline)

    dealine 表示等待到的绝对时间,以毫秒为单位

    功能与 parkNanos(long nanos) 基本一致,只是 parkNanos(long nanos) 等待的是相对时长(纳秒),而 parkUntil(long deadline) 等待的则是绝对时间点(毫秒)

  parkUntil(Object blocker, long deadline)

    功能与 parkUntil(long deadline),只是多了个 Object blocker

    将 parkUntil(Object blocker, long deadline) 与 parkUntil(long deadline) 的关系与 parkNanos(Object blocker, long nanos) 与 parkNanos(long nanos)  的关系进行列表,就好理解了

    JDK 中有些地方用到了 parkUntil(Object blocker, long deadline)

  unpark

  方法体非常简单

  我们来翻一下它的注释

  1、使入参线程的 permit 可用(将 permit 设置成 1)

  2、如果入参线程正阻塞于 park,那么会唤醒入参线程,否则入参线程的下一次 park 不会阻塞

  3、如果入参线程还没有启动,它不会产生任何效果

  4、如果入参线程为null,它不会产生任何效果

  JDK 中有很多地方用到了它

使用场景

  因为 JDK 已经提供了丰富的 API,所以我们平时基本不会直接使用 LockSupport,所以很多人认为 LockSupport 离我们很远

  其实不然,只要我们用到 JUC 下的类来进行并发编程,那么就已经间接用到了 LockSupport 了

  JUC 中线程的阻塞与唤醒的实现,依赖的都是 LockSupport

  线程交替打印

    这是楼主之前遇到的一个面试题,LockSupport 就是其中的一个考点,具体可查看:记一个有意思的面试题 → 线程交替输出问题

    用 LockSupport 是最优的解决方式,不依赖于第三方的同步值,代码简单,逻辑清晰,非常好理解和实现

总结

  1、park 分三类,每类分两种,官方推荐用带 blocker 参数的那一种

    park()、park(Object blocker)

    parkNanos(long nanos)、parkNanos(Object blocker, long nanos)

    parkUntil(long deadline)、parkUntil(Object blocker, long deadline)

  2、park 与 unpark 之间没有严格的调用先后顺序

    permit = 1 表示可用,permit = 0 表示不可用;permit 属于线程私有

    park 消耗 permit,将 permit 从 1 设置成 0;unpark 则将 permit 设置成 1,不管设置前的值是 1 还是 0

    permit 可用,则 park 不会阻塞当前线程,将 permit 设置成 0,线程继续往下执行,否则 park 会阻塞当前线程

    unpark 会设置指定线程的 permit = 1,并唤醒指定的线程

参考

  Java魔法类:Unsafe应用解析

  JVM 常见线上问题 → CPU 100%、内存泄露 问题排查

Java 并发编程(一) → LockSupport 详解的更多相关文章

  1. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  2. java 并发编程lock使用详解

    浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...

  3. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

  4. Java网络编程和NIO详解8:浅析mmap和Direct Buffer

    Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...

  5. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  6. Java网络编程和NIO详解6:Linux epoll实现原理详解

    Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...

  7. Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型

    Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 ...

  8. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  9. Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制

    Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...

  10. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

随机推荐

  1. Vue.js 学习笔记之七:使用现有组件

    5.3 使用现有组件 在之前的五个实验中,我们所演示的基本都是如何构建自定义组件的方法,但在具体开发实践中,并非项目中所有的组件都是需要程序员们自己动手来创建的.毕竟在程序设计领域,"不要重 ...

  2. Linux系统浮动IP的配置

    什么是浮动IP,为什么要配置浮动IP 首先说一下为什么要配置浮动IP. 原文链接:https://blog.csdn.net/readiay/article/details/53538085 现在有一 ...

  3. java例题_21 求 1+2!+3!+...+20!的和

    1 /*21 [程序 21 求阶乘] 2 题目:求 1+2!+3!+...+20!的和 3 程序分析:此程序只是把累加变成了累乘. 4 */ 5 6 /*分析 7 * 1.汲取上一题的教训,这么大的数 ...

  4. springboot的yml中的bruid没有提示

    解决方案: springboot的yml中的bruid没有提示只要在maven加上:druid-spring-boot-starter(注意版本的统一,否则可能会冲突) 效果图:

  5. 博文推荐|多图详解 Apache Pulsar 消息存储模型

    关于 Apache Pulsar Apache Pulsar 是 Apache 软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息.存储.轻量化函数式计算为一体,采用计算与存储分离架构设计,支 ...

  6. 将Java编译为本地代码

    将Java编译为本地代码 通常Java程序的执行流程为:将Java代码编译为Byte Code(字节码),然后JVM执行引擎执行编译好的Byte Code.这是一种中间语言的特性,它的好处就是可以做到 ...

  7. 【算法学习笔记】组合数与 Lucas 定理

    卢卡斯定理是一个与组合数有关的数论定理,在算法竞赛中用于求组合数对某质数的模. 第一部分是博主的个人理解,第二部分为 Pecco 学长的介绍 第一部分 一般情况下,我们计算大组合数取模问题是用递推公式 ...

  8. Oracle-DG 主库将log_archive_dest_state_2远程归档线程参数设置为defer,为什么dg还是处于实时同步状态?

    一.需求,前段时间,墨天伦有个小伙伴咨询了这个问题,搞了测试环境测试下. Oracle-DG 主库将log_archive_dest_state_2远程归档线程参数设置为defer,为什么dg还是处于 ...

  9. 安全开发Java:日志注入,并没那么简单

    摘要:当web工程比较大,历史代码较多时, 应当使用log4j2框架的能力来修改日志注入问题,而不是按照有些博文里写的逐个进化参数的方式. 案例故事 某个新系统上线了,小A在其中开发了个简单的登录模块 ...

  10. (九)Docker-PS 详解

    1. 作用 查看 docker 容器 2. 基本语法 docker ps [OPTIONS] OPTIONS说明: -a :显示所有的容器,包括未运行的. -f :根据条件过滤显示的内容. --for ...