java并发包系列---LockSupport 浅浅的花香味﹌ 2021-06-30 13:06 380阅读 0赞 长久以来对线程阻塞与唤醒经常我们会使用object的wait和notify,除了这种方式,java并发包还提供了另外一种方式对线程进行挂起和恢复,它就是并发包子包locks提供的LockSupport。 LockSupport提供了park和unpark进行线程的挂起和恢复操作,来看一个简单挂起和恢复的简单例子: ![java并发包系列(三)---LockSupport][java_---LockSupport] 由于编辑格式限制,直接贴代码有人反映会显得很杂乱,之后有关代码将直接放图片,上例子中描述了一个场景,周末了某男兴奋的去打游戏了,于是被游戏阻塞了(park),其他的都不干了,这个时候女朋友打来电话,别打游戏了,陪她逛街,把男朋友从游戏中唤醒(unpark)。LockSupport使用方式和wait/notify很类似,LockSupport使用更加灵活,unpark可以先于park进行调用,因为这个特点,我们可以不用担心挂起和恢复时序问题,就如流打开了必须关闭这中类似问题,给我们带来很多编程麻烦。 ![java并发包系列(三)---LockSupport][java_---LockSupport 1] LockSupport底层是有Unsafe提供的两个基本同步语句,这个在关于Unsafe介绍中已经做了分析,LockSupport是对这两个函数的进一步封装,除了例子中方法,它还提供了其他几个功能。 ![java并发包系列(三)---LockSupport][java_---LockSupport 2] 以上为LockSupport属性,Unsafe这个很好理解,整个LockSupport的实现都是基于Unsafe的两个方法,SEED、PROBE、SECONDARY都是Thread类中为了对象上图中三个字段的相对地址偏移量,功能主要用于ThreadLocalRandom类进行随机数生成,它比Random性能要高的多,虽然LockSupport定义这三个字段但是基本没有使用,可能之后JDK会有所变化吧。这里parkBlockerOffset字段,解释起来就是挂起线程对象的偏移地址,对应的是Thread类的parkBlocker。 ![java并发包系列(三)---LockSupport][java_---LockSupport 3] 这个字段可以理解为专门为LockSupport而设计的,它被用来记录线程是被谁堵塞的,当程序出现问题时候,通过线程监控分析工具可以找出问题所在。注释说该对象被LockSupport的getBlocker和setBlocker来获取和设置,且都是通过地址偏移量方式获取和修改的。由于这个变量LockSupport提供了park(Object parkblocker)方法。 ![java并发包系列(三)---LockSupport][java_---LockSupport 4] 代码很好理解获取当前线程,通过偏移量的方式设置parkBlocker的值,将调取unsafe.park把线程挂起,线程被恢复后,修改blocker为null。 那我们可以把文章开篇例子修改的更加应景一些,如下: ![java并发包系列(三)---LockSupport][java_---LockSupport 5] 定义一个blocker者名字叫“游戏”,某男被游戏堵塞park(a),我们通过jstack pid获取当前线程相关信息(jstack用于打印出给定的Java进程ID或core file或远程调试服务的Java堆栈信息): ![java并发包系列(三)---LockSupport][java_---LockSupport 6] 可以看到当前线程状态是WAITING,确实通过unsafe.park挂起的,blocker为一个字符串类型的id为0x…的。通过mat工具可以知道这个id对应得对象即为变量名为game的string对象。若不设置blocker,则是空的,如下: ![java并发包系列(三)---LockSupport][java_---LockSupport 7] 只是将线程进行了挂起,无blocker。 那么LockSupport的park/unpark与wait/notify有啥区别呢?首先wait/notify对线程所起的作用和park/unpark是一样的,如下为使用wait阻塞线程的线程状态: ![java并发包系列(三)---LockSupport][java_---LockSupport 8] 也是waiting,只是方式是调取Object.wait。说明产生的效果是一样,那来继续分析一下两者实现机制。 它俩面向操作的对象不同,通过上述分析,我们知道LockSupport阻塞和唤醒线程直接操作的就是线程,所以从语义上讲,它更符合常理,或者叫更符合语义。而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒。 park/unpark使用起来会更加的灵活、方便。在调用对象的wait之前当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行。而LockSupport则不会,如例子中所示,它可以随意进行park或者unpark。 两则虽然都能更改线程状态为WAITING,但由于实现的机制不同,所以不会产生交集,就是park挂起线程,notify/notifyall是无法进行唤醒的。我们来看个例子。就好比你在打游戏,一个陌生大妈让你逛街去,你应该不会去吧(特殊需求的除外)。如下例子(代码这样写也是绝了,仅仅举例子): ![java并发包系列(三)---LockSupport][java_---LockSupport 9] 使用notify还有一个问题就是,有时候为了保险起见大多数都用notifyall,notify只能唤醒一个线程,假如有两个被阻塞的话,另外一个就悲剧了。 除此之外,park也可以响应中断异常,关于中断异常详讲也需要一大篇文章,这里不做详细分析,来看一下park响应中断过程。 ![java并发包系列(三)---LockSupport][java_---LockSupport 10] 对开篇代码改造一下,还是某男打游戏,且深深陷入其中(park),突然屎意甚浓,于是被其中断(interrupt),然后某男去拉屎去了。最终线程退出了运行,在这里你会发现,park并不会抛出InterruptedException异常。那问题来,不抛出异常,那和正常的unpark有何区别?不能拉屎中断和女朋友召唤效果一样吧。这里就要依赖线程的interruptedstatus,如果线程被中断而退出阻塞该状态会被修改为true。如下可获取到当前interrupted status。 ![java并发包系列(三)---LockSupport][java_---LockSupport 11] 总结起来LockSupport有以下不同和特点: 1. 其实现机制和wait/notify有所不同,面向的是线程。 2. 不需要依赖监视器 3. 与wait/notify没有交集 4. 使用起来更加灵活方便 [java_---LockSupport]: /images/20210527/5d3d3389ca7a4deab3a488daf5c1ba34.png [java_---LockSupport 1]: /images/20210527/d5da876f36b145b7addc571a20f71680.png [java_---LockSupport 2]: /images/20210527/e4ed06df627c40659e6b7bfbd89f83be.png [java_---LockSupport 3]: /images/20210527/3701c038de5846b7ad62129377e739ab.png [java_---LockSupport 4]: /images/20210527/bf611496e34546bfb152af692d61f025.png [java_---LockSupport 5]: /images/20210527/39fbef4cb87b4e6696e796c0b5dfc1f0.png [java_---LockSupport 6]: /images/20210527/a54a1423699f410985df442217255245.png [java_---LockSupport 7]: /images/20210527/b03bdab59ed64894bd27c32a157b0c90.png [java_---LockSupport 8]: /images/20210527/9f0af1f54ea543ef9147965d5cf0e94a.png [java_---LockSupport 9]: /images/20210527/94d5edffb8ee4919a55dbf06a6241a64.png [java_---LockSupport 10]: /images/20210527/f1f373b9370044189fe14f3ed6381bff.png [java_---LockSupport 11]: /images/20210527/0038d7566a0c4bc3beed804df0d0d5da.png
还没有评论,来说两句吧...