【JUC 并发编程】— LockSupport 源码解析
介绍
源码中是这么说的
Basic thread blocking primitives for creating locks and other synchronization classes.
LockSupport 是创建锁和其他同步组件中阻塞线程的基本原语
比如在 AQS 中的 FIFO 队列中,如果前驱节点的状态为 SIGNAL,那么就阻塞当前节点,用的便是 LockSupport
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
许可
LockSupport 与每个使用的线程都会关联一个许可 permit,内部使用 unsafe 类实现。如果许可可用,LockSupport.park() 方法会立即返回,并消费这个许可。LockSupport.unpark() 会使许可可用,但是多次调用 unpark() 并不会增加许可,最多一个。
@Test
public void unparkBeforePark() throws Exception {
System.out.println("before park...");
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("after park...");
}
上面代码先调用 unpark(),当前线程关联的许可可用了,下面的 park() 会立即返回,控制台打印
before park...
after park...
@Test
public void multiUnparkAndPark() throws Exception {
System.out.println("unpark one...");
LockSupport.unpark(Thread.currentThread());
System.out.println("unpark two...");
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("park one...");
LockSupport.park();
System.out.println("park two...");
}
这段代码先 unpark 当前两次,但是许可只有一个,下面的第一个 park 会消费这个许可,第二个 park 就没有许可可用了,当前线程便会阻塞在这里。控制台打印如下
unpark one...
unpark two...
park one...
方法
park & unpark
@Test
public void park() {
System.out.println("before park...");
LockSupport.park();
System.out.println("after park...");
}
阻塞当前线程,控制台只会打印 “before park...”
有三种情况 LockSupport.park() 会返回
- LockSupport.unpark(thread) 被调用,并且参数是当前线程
- thread.interrupter(),当前线程被中断
- 虚假唤醒
unpark() 和中断唤醒线程
@Test
public void wakeUpAfterUnpark() throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before park...");
LockSupport.park();
System.out.println("after park...");
}
});
thread.start();
Thread.sleep(1000L);
//System.out.println("main thread unpark");
//LockSupport.unpark(thread);
System.out.println("main thread interrupter child thread");
thread.interrupt();
}
控制台打印
before park...
main thread interrupter child thread
after park...
需要注意的是,park 方法并不会记录是什么原因导致返回,unpark?还是中断?所以调用者应该检查需要 park 返回的条件,比如线程中断才返回,看下面例子
@Test
public void wakeUpAfterInterrupter() throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("interrupter...");
while (!Thread.currentThread().isInterrupted()) {
LockSupport.park();
}
System.out.println("after park...");
}
});
thread.start();
Thread.sleep(1000L);
System.out.println("main thread interrupter child thread");
thread.interrupt();
//LockSupport.unpark(thread);
}
上面代码中,子线程启动后没有被中断,被阻塞。然后主线程中断该线程,park 方法返回,再次检查中断状态,退出 while 循环。如果中断换成 unpark,while 判断永远为 false,子线程再次被阻塞。
parkNanos & parkUntil
LockSupport.parkNanos(long nanos) 阻塞线程一段时间后返回,参数为纳秒
@Test
public void wakeUpNano() throws Exception {
System.out.println("before park...");
LockSupport.parkNanos(3000000000L);
System.out.println("time is up");
}
LockSupport.parkUntil(long deadline) 阻塞线程到某个时间返回,参数是该时间的毫秒值
@Test
public void wakeUpUntilTime() throws Exception {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 3);
System.out.println("before park...");
LockSupport.parkUntil(instance.getTimeInMillis());
System.out.println("time is up");
}
park(Object blocker)
park 系(包括 parkNanos、parkUntil)方法还允许带个 blocker 参数。当线程被阻塞的时候,该参数会被设置到线程内部。
park(Object blocker) 代码
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// 设置线程的阻塞对象
setBlocker(t, blocker);
// 阻塞线程
unsafe.park(false, 0L);
// 被唤醒的时候设置线程阻塞对象为 null
setBlocker(t, null);
}
其实在 Thread 内部有个 parkBlocker 属性,便是上面的阻塞对象
/**
* The argument supplied to the current call to
* java.util.concurrent.locks.LockSupport.park.
* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
* Accessed using java.util.concurrent.locks.LockSupport.getBlocker
*/
volatile Object parkBlocker;
注释中说的很明白,这个参数是提供给 LockSupport.park() 使用的,设置的方法就是 LockSupport.setBlocker(),获取方法是 LockSupport.getBlocker()。
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return unsafe.getObjectVolatile(t, parkBlockerOffset);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
unsafe.putObject(t, parkBlockerOffset, arg);
}
注意 set 方法和 get 方法的访问权限。set 方法是 private,也就是只能在 LockSupport 内部使用。而 get 方法是 public,可以被任意调用。这也就是这个 blocker 的作用了
线程在阻塞的时候,blocker 会被添加到线程内部(parkBlocker 属性)。当我们使用诊断工具来查看阻塞原因时,诊断工具就可以调用 getBlocker 方法获取 blocker,便可知道当前线程阻塞在哪个对象。官方推荐我们使用 park(Object blocker) 方法来阻塞线程,而且 blocker 设置为 this
看下面例子
@Test
public void parkWithBlocker() throws Exception {
System.out.println("before park...");
LockSupport.park(this);
System.out.println("after park...");
}
代码运行的时候当前线程会阻塞在 LockSupport.park(this) 这里,然后用 jps 查看改程序的 pid
再 jstack 1268
可以看到主线程正处于 WAITING 状态,是因为 parking 而等待。而且还能知道是在 LockSupportDemo 这个类上等待。
例子
LockSupport 源码中给出了一个例子
public class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
// Block while not first in queue or cannot acquire lock
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());
}
}