Java 并发编程(一) → LockSupport 详解
开心一刻
今天突然收到花呗推送的消息,说下个月 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,并唤醒指定的线程
参考
JVM 常见线上问题 → CPU 100%、内存泄露 问题排查
Java 并发编程(一) → LockSupport 详解的更多相关文章
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- java 并发编程lock使用详解
浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- Java网络编程和NIO详解8:浅析mmap和Direct Buffer
Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...
- Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...
- Java网络编程和NIO详解6:Linux epoll实现原理详解
Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...
- Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型
Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 ...
- Java网络编程和NIO详解3:IO模型与Java网络编程模型
Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...
- Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制
Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
随机推荐
- C# 获取Word文本高亮和背景(附vb.net代码)
Word中的文本高亮和背景是通过不同方法来设置的.文本高亮(Text Highlight Color)是通过[字体]中的快速工具栏设置:文本背景(Text Background/Shading)是通过 ...
- python3使用cv2对图像进行基本操作
技术背景 在机器视觉等领域,最基本的图像处理处理操作,可以通过opencv这个库来实现.opencv提供了python的接口,所需安装的库为opencv-python,但是在库的导入的时候一般用的是i ...
- 问题笔记-vueCli3.0打包路径出错
需求:vueCli3.0打包路径出错.解决办法:vueCli3.0打包,新版本更新脚手架做出精简,webpack配置文件需要手动配置.在文件根目录创建一个vue.config.js配置文件.基本版: ...
- 【图像处理】OpenCV+Python图像处理入门教程(六)图像平滑处理
相信很多小伙伴都听过"滤波器"这个词,在通信领域,滤波器能够去除噪声信号等频率成分,然而在我们OpenCV中,"滤波"并不是对频率进行筛选去除,而是实现了图像的 ...
- 【MQ中间件】RabbitMQ -- SpringBoot整合RabbitMQ(3)
1.前言说明 前面一篇博客中提到了使用原生java代码进行测试RabbitMQ实现多种交换机类型的队列场景.但是在项目中我们一般使用SpringBoot项目,而且RabbitMQ天生对于Spring的 ...
- JavaWeb 补充(Json)
HTML DOM alert() 方法 定义和用法 alert() 方法用于显示带有一条指定消息和一个 OK 按钮的警告框. 参数 描述 message 要在 window 上弹出的对话框中显示的纯文 ...
- java 用枚举替换多if-else
1.定义抽象类 package com.polaris.design; /** * @author :shi * @date :Created in 2020/8/18 20:15 * @descri ...
- Salesforce学习之路(六)利用Visualforce Page实现页面的动态刷新功能
Visualforce是一个Web开发框架,允许开发人员构建可以在Lightning平台上本地托管的自定义用户界面.其框架包含:前端的界面设计,使用的类似于HTML的标记语言:以及后端的控制器,使用类 ...
- 记一次 .NET 某教育系统API 异常崩溃分析
一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...
- linux下export命令添加删除环境变量
Linux export命令参数 功能说明:设置或显示环境变量. 语 法:export [-fnp][变量名称]=[变量设置值] 补充说明:在shell中执行程序时,shell会提供一组环境变量. ...