LockSupport是并发工具包中的一个辅助类,在直接的开发过程中很少直接使用,但是它的身影已经遍布各种工具类,众所周知,AbstractQueuedSynchronizer简称(AQS)是Java并发包的基石之一,很多很实用的工具类,如下:
ReentrantLock
ReadLock
WriteLock
Semaphore
CountDownLatch
都是采用AbstractQueuedSynchronizer辅助构建的,而在AbstractQueuedSynchronizer类中却大量采用了LockSupport来操作各种锁的状态,所以我们很有必要来了解学习这个类。
LockSupport的原理是,它最多只允许有一个许可证令牌(permit),如果线程可以得到这个令牌,那么他就能继续运行下去,否则将会处于阻塞状态,注意此时对应的线程状态是WAITTING不是BLOCKED这个后面我们在细说,先来看下这个类的几个方法:
park()
park(Object blocker)
parkNanos(long nanos)
parkNanos(Object blocker, long nanos)
parkUntil(long deadline)
parkUntil(Object blocker, long deadline)
unpark(Thread thread)
getBlocker(Thread t)
(一)park系列的方法
其中park前缀开头的方法,是用来获取许可证的,如果当前线程的许可证令牌存在1个,那么调用park相关方法的线程会立马返回继续执行,同时许可证个数就变成0个,如果再继续调用park方法,那么当前线程就会阻塞处于等待状态(WAITTING),除非有以下几种情况,才会被唤醒:
(1)任何其他的线程对该线程对象调用了unpark方法,归还了令牌,那么就会解除等待。这里需要注意令牌最多只会有一个,多次调用unpark方法令牌个数是不会随之累加的。
(2)任何其他的线程,对该线程调用了interrupt方法,打断了线程
(3)由于硬件操作系统不可预知的原因,导致了虚假唤醒
(4)调用了下面的一系列方法,可以在时间过期后自动唤醒。
parkNanos(long nanos)
parkNanos(Object blocker, long nanos)
parkUntil(long deadline)
parkUntil(Object blocker, long deadline)
所以park方法,常用的编程模板是:
while (!canProceed()) {
LockSupport.park(this);
}
这里面while循环的作用,就是为了避免虚假唤醒的情况,此外在配合使用自旋锁的时候,park方法可以使得当前线程进入等待状态,从而可以避免大量自旋导致的cpu资源的浪费。
(二)unpark方法
调用这个方法,可以保证归还一个许可证令牌,从而保证下次调用park方法的线程可以直接立即返回运行,这里需要注意的是对于一个没有启动的线程,这里不保证有任何影响。
(三)关于park和getBlocker方法的对象参数的作用
park(Object blocker)
getBlocker(Thread t)
park方法的blocker参数的作用是可以传入一个对象,记录一些监控信息,这样当线程处于阻塞时候,我们可以调用getBlocker方法读取这个对象,从而获取这个阻塞线程的一些情况,注意这里获取的仅仅是最后一次park的对象信息,因为park方法可以调用无数次,所以这里只能保证获取最新的自定义的对象监控信息。
(四)使用例子
关于park和unpark一般都是成对出现的,下面看一个oracle官网给的使用LockSupport做的一个公平队列锁的代码:
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
//先把自己加到队列里面
waiters.add(current);
// Block while not first in queue or cannot acquire lock
//peek取出头元素但不移除,判断头元素是否等当前线程 ,如果不是当前元素,直接进入循环
//如果头不是当前线程,那么判断CAS指令,如果成功直接执行代码,否则进入循环阻塞
while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
LockSupport.park(this);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
}
waiters.remove();
if (wasInterrupted) // reassert interrupt status on exit
current.interrupt();
}
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
(五)相关问题
(1)park和unpark方法在官网说明书里面并没有提到它们有happends-before关系,所以这里不能依赖它们保证相关的原子性,可见性和有序性。
(2)park调用会导致当前的线程处于WAITTING或者WAITTING_TIMED状态,当调用unpark方法时并不会立即进入RUNNABLE状态,而是看当前有没有线程已经占用锁,如果有它们会先变成BLOCKED状态,然后在变成RUNNABLE状态
(3)WAITTING与BLOCKED状态的区别,简单的理解WAITTING一般需要有人通知才能唤醒,而BLOCKED通常是只要对象的锁没人占用就可以继续运行,如果有人占用则进入BLOCKED阻塞
(4)LockSupport类一般不会直接出现在代码里,绝大多数时候我们只需要使用AbstractQueuedSynchronizer这个封装好的工具框架类即可。
总结:
本文详细的介绍了LockSupport工具类的方法api和功能说明,并且分析了该类的相关注意事项和问题,由于该类是AQS框架的一个重要组成部分,所以我们很有必要去清晰的掌握和了解它。