java并发包系列---LockSupport

长久以来对线程阻塞与唤醒经常我们会使用object的wait和notify,除了这种方式,java并发包还提供了另外一种方式对线程进行挂起和恢复,它就是并发包子包locks提供的LockSupport。

LockSupport提供了park和unpark进行线程的挂起和恢复操作,来看一个简单挂起和恢复的简单例子:

java并发包系列---LockSupport_第1张图片

由于编辑格式限制,直接贴代码有人反映会显得很杂乱,之后有关代码将直接放图片,上例子中描述了一个场景,周末了某男兴奋的去打游戏了,于是被游戏阻塞了(park),其他的都不干了,这个时候女朋友打来电话,别打游戏了,陪她逛街,把男朋友从游戏中唤醒(unpark)。LockSupport使用方式和wait/notify很类似,LockSupport使用更加灵活,unpark可以先于park进行调用,因为这个特点,我们可以不用担心挂起和恢复时序问题,就如流打开了必须关闭这中类似问题,给我们带来很多编程麻烦。

java并发包系列(三)---LockSupport

LockSupport底层是有Unsafe提供的两个基本同步语句,这个在关于Unsafe介绍中已经做了分析,LockSupport是对这两个函数的进一步封装,除了例子中方法,它还提供了其他几个功能。

java并发包系列---LockSupport_第2张图片

以上为LockSupport属性,Unsafe这个很好理解,整个LockSupport的实现都是基于Unsafe的两个方法,SEED、PROBE、SECONDARY都是Thread类中为了对象上图中三个字段的相对地址偏移量,功能主要用于ThreadLocalRandom类进行随机数生成,它比Random性能要高的多,可阅读该篇文章了解详细(https://my.oschina.net/adan1/blog/159371),虽然LockSupport定义这三个字段但是基本没有使用,可能之后JDK会有所变化吧。这里parkBlockerOffset字段,解释起来就是挂起线程对象的偏移地址,对应的是Thread类的parkBlocker。

java并发包系列---LockSupport_第3张图片

这个字段可以理解为专门为LockSupport而设计的,它被用来记录线程是被谁堵塞的,当程序出现问题时候,通过线程监控分析工具可以找出问题所在。注释说该对象被LockSupport的getBlocker和setBlocker来获取和设置,且都是通过地址偏移量方式获取和修改的。由于这个变量LockSupport提供了park(Object parkblocker)方法。

java并发包系列---LockSupport_第4张图片

代码很好理解获取当前线程,通过偏移量的方式设置parkBlocker的值,将调取unsafe.park把线程挂起,线程被恢复后,修改blocker为null。

那我们可以把文章开篇例子修改的更加应景一些,如下:

java并发包系列---LockSupport_第5张图片

定义一个blocker者名字叫“游戏”,某男被游戏堵塞park(a),我们通过jstack pid获取当前线程相关信息(jstack用于打印出给定的Java进程ID或core file或远程调试服务的Java堆栈信息):

java并发包系列---LockSupport_第6张图片

可以看到当前线程状态是WAITING,确实通过unsafe.park挂起的,blocker为一个字符串类型的id为0x…的。通过mat工具可以知道这个id对应得对象即为变量名为game的string对象。若不设置blocker,则是空的,如下:

java并发包系列---LockSupport_第7张图片

只是将线程进行了挂起,无blocker。

那么LockSupport的park/unpark与wait/notify有啥区别呢?首先wait/notify对线程所起的作用和park/unpark是一样的,如下为使用wait阻塞线程的线程状态:

java并发包系列---LockSupport_第8张图片

也是waiting,只是方式是调取Object.wait。说明产生的效果是一样,那来继续分析一下两者实现机制。

它俩面向操作的对象不同,通过上述分析,我们知道LockSupport阻塞和唤醒线程直接操作的就是线程,所以从语义上讲,它更符合常理,或者叫更符合语义。而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒。

park/unpark使用起来会更加的灵活、方便。在调用对象的wait之前当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行。而LockSupport则不会,如例子中所示,它可以随意进行park或者unpark。

两则虽然都能更改线程状态为WAITING,但由于实现的机制不同,所以不会产生交集,就是park挂起线程,notify/notifyall是无法进行唤醒的。我们来看个例子。就好比你在打游戏,一个陌生大妈让你逛街去,你应该不会去吧(特殊需求的除外)。如下例子(代码这样写也是绝了,仅仅举例子):

java并发包系列---LockSupport_第9张图片

使用notify还有一个问题就是,有时候为了保险起见大多数都用notifyall,notify只能唤醒一个线程,假如有两个被阻塞的话,另外一个就悲剧了。

除此之外,park也可以响应中断异常,关于中断异常详讲也需要一大篇文章,这里不做详细分析,来看一下park响应中断过程。

java并发包系列---LockSupport_第10张图片

对开篇代码改造一下,还是某男打游戏,且深深陷入其中(park),突然屎意甚浓,于是被其中断(interrupt),然后某男去拉屎去了。最终线程退出了运行,在这里你会发现,park并不会抛出InterruptedException异常。那问题来,不抛出异常,那和正常的unpark有何区别?不能拉屎中断和女朋友召唤效果一样吧。这里就要依赖线程的interruptedstatus,如果线程被中断而退出阻塞该状态会被修改为true。如下可获取到当前interrupted status。

java并发包系列---LockSupport_第11张图片

总结起来LockSupport有以下不同和特点:

  1. 其实现机制和wait/notify有所不同,面向的是线程。

  2. 不需要依赖监视器

  3. 与wait/notify没有交集

  4. 使用起来更加灵活方便

你可能感兴趣的:(java多线程)