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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/266399.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php网址变量怎么输出,【PHP网站】如何使用dedecms v5.7前台模版里输出变量

dedecmsv5.7是PHP网站内容管理系统&#xff0c;本篇文章将介绍如何使用dedecmsv5.7 前台模版来输出变量&#xff0c;具有一定参考意义&#xff0c;感兴趣的朋友了解一下吧。如何在PHP文件查询出来的数据赋值给前端页面展示出来&#xff1f;例如&#xff1a;PHP文件&#xff1a;…

全国计算机等级考试题库二级C操作题100套(第48套)

第48套&#xff1a; 给定程序中&#xff0c;函数fun的功能是&#xff1a;将形参s所指字符串中的数字字符转换成对 应的数值&#xff0c;计算出这些数值的累加和作为函数值返回。 例如&#xff0c;形参s所指的字符串为&#xff1a;abs5def126jkm8&#xff0c;程序执行后的输出结…

Nginx内核参数相关的优化设定

Nginx内核参数在使用的时候有不少问题需要我们解决&#xff0c;其中在优化方面就需要我们格外的注意。在下面就是对Nginx内核参数优化的详细介绍&#xff0c;希望大家有所收获。 关于Nginx内核参数的优化&#xff1a; net.ipv4.tcp_max_tw_buckets 6000 timewait的数量&#x…

python3 爬虫 requests安装_BOSS直聘招聘信息获取之爬虫工具分析

点击蓝色“不太灵光的程序员”关注我哟加个“星标”&#xff0c;每天上午 09:30&#xff0c;干货推送&#xff01;文中使用的组件库仅限于Python语言&#xff0c;由于最近收到一些同学的留言说&#xff0c;按照网上的教程一步一步的学习&#xff0c;却频繁的出现报错&#xff0…

假期周进度总计(四)

本周学习利用OEM工具创建删除表以及对表内数据进行增删改查的操作 一切均通过此工具进行&#xff0c;然后还学到了用SQL *Plus进行相应操作 本周每天4.5个小时&#xff0c;出现的错误就是点击的误操作&#xff0c;可以改正&#xff0c;下周进一步学习Oracle基本操作转载于:http…

Java集合框架(3)

Map(和Collection<E>一样都是集合框架的顶层接口) |--Hashtable:底层是哈希表数据结构&#xff0c;不可以用null对象作为键或值。它是线程同步的。 |--HashMap&#xff1a;底层是哈希表。允许使用null键null值&#xff0c;该集合是不同步的&#xff0c;效率高&#xff0c…

php函数从数组中取出指定的数目,PHP数组函数

1.array_rand()从数组中随机取出一个或多个元素(返回值是&#xff1a;随机元素的键)$arr[js,css,25,php,30];printf(%s,print_r($arr,true));//随机去2个元素$resarray_rand($arr,2);printf(%s,print_r($res,true));2.array_replace()使用后面数组元素相同 key 的值替换 array1…

存储过程 not supported yet_让我们来看看+Redis如何存储和计算一亿用户的活跃度

1前段时间&#xff0c;在网上看到一道面试题&#xff1a;如何用redis存储统计1亿用户一年的登陆情况&#xff0c;并快速检索任意时间窗口内的活跃用户数量。觉得很有意思&#xff0c;就仔细想了下 。并做了一系列实验&#xff0c;自己模拟了下 。还是有点收获的&#xff0c;现整…

HBase查询优化

1.概述 HBase是一个实时的非关系型数据库&#xff0c;用来存储海量数据。但是&#xff0c;在实际使用场景中&#xff0c;在使用HBase API查询HBase中的数据时&#xff0c;有时会发现数据查询会很慢。本篇博客将从客户端优化和服务端优化两个方面来介绍&#xff0c;如何提高查询…

全国计算机等级考试题库二级C操作题100套(第49套)

第49套&#xff1a; 给定程序中&#xff0c;函数fun的功能是&#xff1a;将形参s所指字符串中所有ASCII码值小于97 的字符存入形参t所指字符数组中&#xff0c;形成一个新串&#xff0c;并统计出符合条件的字符个数 作为函数值返回。 例如&#xff0c;形参s所指的字符串为&…

NEC SV8100电话交换机配置梓博电话计费系统

为了节约办公成本&#xff0c;规范电话使用。公司最近为NEC SV8100电话交换机系统上线了一套梓博的电话计费系统。先将配置过程分享给各位。 1、登录电话交换机系统&#xff08;默认用户名tech、密码12345678&#xff09; 2、点击系统数据配置按钮 2、通过10-01选项调整电话交换…

判断闰年 php,PHP怎么判断一年是否为闰年?

判断是否为闰年的条件是满足下列二者条件之一&#xff1a;年号能被4整除、但不能被100整除&#xff0c;二是年号能被4整除&#xff0c;又能被400整除。那么PHP怎么判断一年是否为闰年&#xff1f;下面本篇文章就来给大家介绍一下使用PHP判断一年是否为闰年的方法&#xff0c;希…

redis desktop manager_面试官:Redis分布式锁如何解决锁超时问题?

Java面试笔试面经、Java技术每天学习一点Java面试关注不迷路作者&#xff1a;wangzaiplus来源&#xff1a;https://www.jianshu.com/u/8cb4591440ca一、前言关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完…

全国计算机等级考试题库二级C操作题100套(第50套)

第50套&#xff1a; 给定程序中&#xff0c;函数fun的功能是:有NN矩阵&#xff0c;以主对角线为对称线&#xff0c;对称元素相加并将结果存放在左下三角元素中&#xff0c;右上三角元素置为0。例如&#xff0c;若N3&#xff0c;有下列矩阵&#xff1a; 1 2 3 4 5 6 7 8 9 计算…

《CLR via C#》读书笔记 之 参数

第九章 参数 2013-02-27 9.3 以传引用的方式向方法传递参数 默认情况下&#xff0c;CLR假定所有方法参数都是传值的。当传递引用类型的对象时&#xff0c;也默认是传值的&#xff0c;只不过这个值是引用&#xff08;指针&#xff09;本身。 CLR允许以传引用的方式传递参数。在C…

学习笔记整理之模式化方法

第一步 要分清要用那个不变的参数去实现 &#xff08;比如我现在要用 ID进行验证 则&#xff0c;验证完毕后实现的抽象方法的参数是 操作数&#xff0c;所以操作 的方法的参数是操作数&#xff09;先new 个要实现的方法比如 New StudentManger(id,name) 此方法要把继承的抽象的…

php组合查询,PHP组合查询多条件查询实例代码第1/2页

先向大家说明需求&#xff1a;按照我们系统的要求&#xff0c;我们将通过部门名称、员工姓名、PC名称、IP地址等等字段来进行组合查询从而得到想要的数据结果。那么&#xff0c;为了简单起见&#xff0c;我们用两个条件(部门名称、员工姓名)的组合查询来向大家说明这一技术技巧…

python print 换行_Python学习 | Python的基础语法

Python 语言与 Perl&#xff0c;C 和 Java 等语言有许多相似之处。但是&#xff0c;也存在一些差异&#xff0c;编写Paython程序之前需要对语法有所了解&#xff0c;才能编写规范的Python程序。一、行和缩进Python最大的特点之一就是Python 的代码块不使用大括号 {}了&#xff…

解决linux下source /etc/profile关闭终端失效问题

本来想配置环境变量的&#xff0c;看网上和博客上很多说改/etc/profile&#xff0c;然后source /etc/profile之后就可以永久保存使环境变量生效&#xff0c;但是终端一关闭&#xff0c;就环境变量就失效了&#xff0c;其他终端也用不了。网上有说在当前用户目录下创建.bash_pro…

bind php,PHP – bind_result到数组

我正在为一个返回多个结果的查询使用一个预准备语句,我想在一个数组中使用它.但是bind_result不能用于数组,所以我就是这样做的&#xff1a;$read_items $db->stmt_init();$read_items->prepare("SELECT item_id, item_name FROM items");$read_items->exe…