LockSupport的源码实现原理以及应用
一、为什么使用LockSupport类
如果只是LockSupport在使用起来比Object的wait/notify简单,
那还真没必要专门讲解下LockSupport。最主要的是灵活性。
上边的例子代码中,主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。如果去掉Thread.sleep()调用,代码如下:
note:这个场景需要注意一下 防止在业务场景中出现这种bug。
public class TestObjWait {public static void main(String[] args)throws Exception {final Object obj = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {int sum = 0;for(int i=0;i<10;i++){sum+=i;}try {synchronized (obj){obj.wait();}}catch (Exception e){e.printStackTrace();}System.out.println(sum);}});A.start(); //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法//Thread.sleep(1000);synchronized (obj){obj.notify();}} }
多运行几次上边的代码,有的时候能够正常打印结果并退出程序,但有的时候线程无法打印结果阻塞住了。原因就在于:主线程调用完notify后,线程A才进入wait方法,
导致线程A一直阻塞住。由于线程A不是后台线程,所以整个程序无法退出。
那如果换做LockSupport呢?LockSupport就支持主线程先调用unpark后,线程A再调用park而不被阻塞吗?是的,没错。代码如下:
public class TestObjWait {public static void main(String[] args)throws Exception {final Object obj = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {int sum = 0;for(int i=0;i<10;i++){sum+=i;}LockSupport.park();System.out.println(sum);}});A.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法//Thread.sleep(1000);LockSupport.unpark(A);} }
不管你执行多少次,这段代码都能正常打印结果并退出。这就是LockSupport最大的灵活所在。
总结一下,LockSupport比Object的wait/notify有两大优势:
①LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
②unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。
三、应用广泛
LockSupport在Java的工具类用应用很广泛,咱们这里找几个例子感受感受。
以Java里最常用的类ThreadPoolExecutor为例。先看如下代码:
public class TestObjWait {public static void main(String[] args)throws Exception {ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1000, TimeUnit.SECONDS,queue);Future<String> future = poolExecutor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(5);return "hello";}});String result = future.get();System.out.println(result);} }
代码中我们向线程池中扔了一个任务,然后调用Future的get方法,同步阻塞等待线程池的执行结果。
这里就要问了:get方法是如何组塞住当前线程?线程池执行完任务后又是如何唤醒线程的呢?
咱们跟着源码一步步分析,先看线程池的submit方法的实现:
在submit方法里,线程池将我们提交的基于Callable实现的任务,封装为基于RunnableFuture实现的任务,然后将任务提交到线程池执行,并向当前线程返回RunnableFutrue。
进入newTaskFor方法,就一句话:return new FutureTask<T>(callable);
所以,咱们主线程调用future的get方法就是FutureTask的get方法,线程池执行的任务对象也是FutureTask的实例。
接下来看看FutureTask的get方法的实现:
比较简单,就是判断下当前任务是否执行完毕,如果执行完毕直接返回任务结果,否则进入awaitDone方法阻塞等待。
awaitDone方法里,首先会用到上节讲到的cas操作,将线程封装为WaitNode,保持下来,以供后续唤醒线程时用。再就是调用了LockSupport的park/parkNanos组塞住当前线程。
上边已经说完了阻塞等待任务结果的逻辑,接下来再看看线程池执行完任务,唤醒等待线程的逻辑实现。
前边说了,咱们提交的基于Callable实现的任务,已经被封装为FutureTask任务提交给了线程池执行,任务的执行就是FutureTask的run方法执行。如下是FutureTask的run方法:
c.call()就是执行我们提交的任务,任务执行完后调用了set方法,进入set方法发现set方法调用了finishCompletion方法,想必唤醒线程的工作就在这里边了,看看代码实现吧:
没错就在这里边,先是通过cas操作将所有等待的线程拿出来,然后便使用LockSupport的unpark唤醒每个线程。
在使用线程池的过程中,不知道你有没有这么一个疑问:线程池里没有任务时,线程池里的线程在干嘛呢?
答案是 线程会调用队列的take方法阻塞等待新任务。那队列的take方法是不是也跟Future的get方法实现一样呢?
以ArrayBlockingQueue为例,take方法实现如下:
与想象的有点出入,他是使用了Lock的Condition的await方法实现线程阻塞。但当我们继续追下去进入await方法,发现还是使用了LockSupport:
限于篇幅,jdk里的更多应用就不再追下去了。
四、LockSupport的实现
学习要知其然,还要知其所以然。接下来不妨看看LockSupport的实现。
进入LockSupport的park方法,可以发现它是调用了Unsafe的park方法,这是一个本地native方法,只能通过openjdk的源码看看其本地实现了,可以看出底层的源码是 C++实现的了;
它调用了线程的Parker类型对象的park方法,如下是Parker类的定义:主要看的 私有成员 构造函数 析构函数 以及其 parker和 unparker 方法。
类中定义了一个int类型的_counter变量,咱们上文中讲灵活性的那一节说,可以先执行unpark后执行park,就是通过这个变量实现,看park方法的实现代码(由于方法比较长就不整体截图了):
park方法会调用Atomic::xchg方法,这个方法会原子性的将_counter赋值为0,并返回赋值前的值。如果调用park方法前,_counter大于0,则说明之前调用过unpark方法,所以park方法直接返回。将_counterf 数值置为0;
接着往下看:
实际上Parker类用Posix的mutex,condition来实现的阻塞唤醒。如果对mutex和condition不熟,可以简单理解为mutex就是Java里的synchronized,condition就是Object里的wait/notify操作。
park方法里调用pthread_mutex_trylock方法,就相当于Java线程进入Java的同步代码块,然后再次判断_counter是否大于零,如果大于零则将_counter设置为零。最后调用pthread_mutex_unlock解锁,
相当于Java执行完退出同步代码块。如果_counter不大于零,则继续往下执行pthread_cond_wait方法,实现当前线程的阻塞。
最后再看看unpark方法的实现吧,这块就简单多了,直接上代码:
图中的1和4就相当于Java的进入synchronized和退出synchronized的加锁解锁操作,代码2将_counter设置为1,
同时判断先前_counter的值是否小于1,即这段代码:if(s<1) ,如果大于等于1,则就不会有线程被park,所以方法直接执行完毕,如果小于1 说明有线程被 park 了 就会执行代码3,来唤醒被阻塞的线程。
通过阅读LockSupport的本地实现,我们不难发现这么个问题:多次调用unpark方法和调用一次unpark方法效果一样,因为都是直接将_counter赋值为1,而不是加1。简单说就是:线程A连续调用两次LockSupport.unpark(B)方法唤醒线程B,然后线程B调用两次LockSupport.park()方法, 线程B依旧会被阻塞。因为两次unpark调用效果跟一次调用一样,只能让线程B的第一次调用park方法不被阻塞,第二次调用依旧会阻塞。
转载于:https://www.cnblogs.com/gxyandwmm/p/9419129.html
LockSupport的源码实现原理以及应用相关推荐
- 【Java 虚拟机原理】JDK 体系结构 | Java 源码运行原理 | Java 虚拟机内存
文章目录 一.JDK 体系结构 二.Java 源码运行原理 三.Java 虚拟机内存结构 一.JDK 体系结构 JDK 体系结构 : 下图所有的内容都是 JDK 体系中的组成元素 ; Java Lan ...
- 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理
上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...
- 艾默生充电15kw+台达三相PFC源程序 艾默生充电桩15kw模块+台达三相PFC源码,软件源码加原理 图BOM
艾默生充电15kw+台达三相PFC源程序 艾默生充电桩15kw模块+台达三相PFC源码,软件源码加原理 图BOM 艾默生充电桩15kw模块原版软件源码含核心算法,PFC+DCDC双DSP数字控制,原理 ...
- ViewDragHelper(二)- 源码及原理解读(进阶篇)
声明:本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本篇为该系列的第二篇,侧重讲解ViewDragHelper 的实现原理和源码逻辑,以及它所提供的Callback. 目录 Vi ...
- SpringCloud微服务注册中心如何承载大型系统的千万级访问?源码及原理分析
2019独角兽企业重金招聘Python工程师标准>>> 问题起源 Spring Cloud架构体系中,Eureka是一个至关重要的组件,它扮演着微服务注册中心的角色,所有的服 ...
- concurrenthashmap实现原理_Mybatis:PageHelper分页插件源码及原理剖析
PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,其实我并不想加上好用两个字,但是为了表扬插件作者开源免费的崇高精神,我毫不犹豫的加上了好用一词作为赞美. 原本以为分页插件, ...
- PageHelper分页插件源码及原理剖析
摘要: com.github.pagehelper.PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件. PageHelper是一款好用的开源免费的Mybatis第三方物理分页 ...
- HashMap源码及原理
HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...
- oracle 分页_Mybatis:PageHelper分页插件源码及原理剖析
点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:祖大俊 my.oschina.net/zudajun/blog/745232 Pag ...
最新文章
- 如何高效入门 PyTorch ?
- 谷歌和伯克利分校的新工作:规模化大场景的神经绘制方法Block-NeRF
- spring配置异步执行
- CCNA-(9)-思科交换机特点
- 科大星云诗社动态20210806
- 初次就这么给了你(Django-rest-framework)
- MySQL(一)面试集合
- Install Kernel 3.10 on CentOS 6.5
- Extjs grid增加或删除列后记住滚动条的位置
- java零基础Ⅰ-- 1.java 概述
- c语言 exec sql编程,C语言采用嵌入式方式操作数据库exec_sql.doc
- 【优秀毕设V2.0】基于树莓派的OpenCV-Python摄像头人脸追踪及手势识别、网络地址推流及远程控制系统(多功能系统、含演示视频)
- android 分享文件功能实现
- [UE4]传送门:场景切换
- 【GANs】Conditional Generative Adversarial Nets
- 什么是FDM 3D打印技术?
- ORB-SLAM2环境配置以及运行详解(一):ROS与非ROS环境下的安装、编译、离线数据集测试
- 单片机c语言中延时函数的作用,单片机中C语言延时函数
- QT程序按钮效果制作
- MBA联考-20101126