LockSupport用法
# 概述
此类与使用它的每个线程关联一个许可(与Semaphore
类似),用于创建锁和其他同步类的基本线程阻塞原语。
如果许可证可用, 调用park
方法将立即返回,并在此过程中消耗许可证,否则可能会阻塞。如果许可证尚不可用,则调用unpark
可使许可证可用。 (但与信号量不同,许可证不会累积。最多有一个。)
也就是说如果先调用unpark
,再调用一次park
方法不会阻塞。unpark
方法调用多次,许可证只有一个,第二次调用park
方法时任然会阻塞。
park
方法也可以在任何其他时间“无缘无故”地返回,因此通常必须在循环中调用,该循环在返回时重新检查条件。
从这个意义上说, park是“忙等待”的优化,不会浪费太多时间,但必须与unpark
搭配使用。
# 类属性
public class LockSupport {
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
//获取UnSafe类实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
//获取parkBlocker字段在Thread对象中的内存偏移量
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
类加载时获取Unsafe
实例,获取Thread
# 构造函数
private LockSupport() {} // Cannot be instantiated.
只有一个私有构造函数,无法被实例化。
# 核心函数
主要使用sun.misc.UnSafe
类中的park``unPark
方法进行阻塞和唤醒
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
2
3
park
函数阻塞线程,等待许可可用。线程将会休眠直到发生以下情况:
其他线程调用
unpark
唤醒该线程其他线程中断当前线程
达到指定的等待时间
调用虚假地(即无缘无故地)返回
由于第四点的存在,所以park
需要在循环中使用,在唤醒后检查条件,未满足条件再次调用park
。当time
为绝对时间时,isAbsolute
为true
,否则,isAbsolute
为false
。当time
为0时,表示无限等待,直到unpark
发生。
unpark
函数使给定线程的许可可用(如果它尚不可用)。如果线程在park
上被阻塞,那么它将解除阻塞。否则,保证其下一次调用park
不会阻塞。如果给定线程尚未启动,则不能保证此操作有任何效果
# park
public static void park() {
UNSAFE.park(false, 0L);
}
2
3
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
2
3
4
5
6
7
8
9
10
两个方法的主要区别在于设置bloker
。线程被唤醒后主动清除bloker
,以免影响在之后的park
中被认为是新设置的broker
。
# parkNanos
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
2
3
4
5
6
7
8
支持设置阻塞毫秒数
# parkUntil
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
2
3
4
5
6
支持设置阻塞绝对时间。
# unpark
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
2
3
4
使给定线程的许可可用(如果它尚不可用)。如果线程在park
上被阻塞,那么它将解除阻塞。否则,保证其下一次调用park
不会阻塞。如果给定线程尚未启动,则不能保证此操作有任何效果。
# getBlocker
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
2
3
4
5
获取该线程最近一次park
时设置的阻塞对象,如果线程已被唤醒,则返回null
。这是一个瞬时快照,获取阻塞对象快照后,线程可能已被唤醒,或者被设置了另一个阻塞对象。
# 示例用法
这是一个先进先出不可重入锁:
class FIFOMutex {
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
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());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
需要注意的是park
方法并不会释放锁,使用不当,也可能造成死锁。
public static void main(String[] args){
try {
Thread t0 = new Thread() {
public void run() {
synchronized (this) {
System.out.println("线程t0 调用park");
LockSupport.park();
}
}
};
t0.start();
Thread.sleep(2*1000);
System.out.println("main线程准备调用unpark");
synchronized (t0) {
LockSupport.unpark(t0);
System.out.println("main线程调用unpark");
}
}catch (Throwable t) {
System.out.println("Caught in main: " + t);
t.printStackTrace();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
由于t0线程调用park
方法并未释放在对象t0上的锁,main线程获取t0对象上的锁时被阻塞,导致死锁。