开心一刻

  今天突然收到花呗推送的消息,说下个月 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. fianl关键词

    一.final关键字概述 final关键字具有最终或不可改变的含义,可用于修饰类.变量.方法.因此被final修饰的类.变量.方法具有以下特征: --final修饰的类不能被继承: --final修饰 ...

  2. 「HTML+CSS」--自定义按钮样式【001】

    前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...

  3. 巧用 SVG 滤镜还能制作表情包?

    本文将介绍一些使用 SVG feTurbulence 滤镜实现的一些有趣.大胆的的动效. 系列另外两篇: 有意思!强大的 SVG 滤镜 有意思!不规则边框的生成方案 背景 今天在群里面聊天,看到有人发 ...

  4. 「Leetcode-算法_MId1006」从单栈到双栈

    Mid 1006 笨阶乘 栈/后缀 运算优化 + 栈 思路描述 每四个数一组 这四个数中前三个会进行乘.除 然后与最后一个相加 Stack 入前三个之和 与 最后一个数 以 4 举例 运算式 4 * ...

  5. Redis生产环境节点宕机问题报错及恢复排错

    Redis故障发现 主观下线 当cluster-node-timeout时间内某节点无法与另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态. 客观下线 当某个节点判断另一个节点主观下 ...

  6. redis安装、配置、启动

    一.redis默认安装位置:/usr/local/bin redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何,服务启动起来后执行redis-check-aof:修复 ...

  7. 电梯也能无为而治——oo第二单元作业总结

    oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...

  8. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的DTR模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的DTR模式. 前两篇文章 <IS25WP系列Dummy Cycle设置> 与 < ...

  9. (二) LDAP 安装

    LDAP 安装 参考:https://blog.51cto.com/bigboss/2341986

  10. JAVAEE_Servlet_15_HttpServletRequest中常用方法(二)

    HttpServletRequest中常用方法 ## HttpServletRequest中的第二类方法 获取各种路径 和 IP地址 * 获取路径和地址 - 获取ServletContext上下文对象 ...