JDK中的rt.jar包里面的LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。
LockSupport是使用Unsafe类实现的,下面介绍LockSupport中的几个主要函数。
如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。
如下代码直接在main函数里面调用park方法,最终只会输出begin park!,然后当前线程被挂起,这是因为在默认情况下调用线程是不持有许可证的。
在其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,调用park方法而被阻塞的线程会返回。
另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。
所以在调用park方法时最好也使用循环条件判断方式。
需要注意的是,因调用park()方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常。
当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread线程持有。
如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒。
如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立刻返回。
该代码会输出
下面再来看一个例子以加深对park和unpark的理解。
该代码会输出
上面代码首先创建了一个子线程thread,子线程启动后调用park方法,由于在默认情况下子线程没有持有许可证,因而它会把自己挂起。
主线程休眠1s是为了让主线程调用unpark方法前让子线程输出child thread beginpark!并阻塞。
主线程然后执行unpark方法,参数为子线程,这样做的目的是让子线程持有许可证,然后子线程调用的park方法就返回了。
park方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足则还需要再次调用park方法。
例如,根据调用前后中断状态的对比就可以判断是不是因为被中断才返回的。
为了说明调用park方法后的线程被中断后会返回,我们修改上面的例子代码,删除LockSupport.unpark(thread);,然后添加thread.interrupt();,具体代码如下。
输出结果为
你调用unpark(thread)方法子线程也不会结束。
和park方法类似,如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.parkNanos(long nanos)方法后会马上返回。
该方法的不同在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后修改为自动返回。
另外park方法还支持带有blocker参数的方法void park(Object blocker)方法,当线程在没有持有许可证的情况下调用park方法而被阻塞挂起时,这个blocker对象会被记录到该线程内部。
使用诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用getBlocker(Thread)方法来获取blocker对象的,所以JDK推荐我们使用带有blocker参数的park方法,并且blocker被设置为this,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了。
Thread类里面有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的线程的成员变量里面。
相比park(Object blocker)方法多了个超时时间。
其中参数deadline的时间单位为ms,该时间是从1970年到现在某一个时间点的毫秒值。
这个方法和parkNanos(Object blocker,long nanos)方法的区别是,后者是从当前算等待 nanos秒时间,而前者是指定一个时间点,比如需要等到2017.12.11日 12:00:00,则把这个时间点转换为从1970年到这个时间点的总毫秒数。