共计 3352 个字符,预计需要花费 9 分钟才能阅读完成。
前言
LockSupport 是 rt.jar 下的工具类,它的作用是挂起和唤醒线程,它在 JUC 很多同步组件中都会用到,比方 AQS。LockSupport 类与每个应用它的线程都会关联一个许可证,在默认状况下调用 LockSupport 类办法的线程是没有许可证的。LockSupport 外部是用 Unsafe 的 park 和 unpark 办法实现的。
次要办法
(1)void park()
调用 park()办法会阻塞以后线程,除非以后线程曾经拿到了与 LockSupport 关联的许可证。当其余线程调用 unpark(Thread thread)办法并且将以后线程作为参数时,或者调用以后线程的 interrupt()办法时,以后线程会从阻塞状态返回。然而,须要留神的是,与调用 sleep(),wait()等办法不同,调用 park()办法被中断返回时,不会抛出 InterruptedException 异样。还有须要留神的是,park()办法返回时不会通知你是什么起因返回(unpark 或 interrupt),但咱们能够依据调用前后的中断标记是否扭转来判断是否因为 interrupt 返回的。
(2)void unpark(Thread thread)
线程调用 unpark 办法时,如果参数线程 thread 没有持有许可证,则让其持有,如果 thread 在此前因调用 park 而被阻塞,则调用 unpark 后,该线程会被唤醒。如果 thread 之前没有调用 park,则调用 unpark 后,再调用 park,线程会立刻返回,而不会阻塞挂起。用代码来阐明:
import java.util.concurrent.locks.LockSupport;
public class UnparkDemo {public static void main(String[] args) {System.out.println("give current thread a permit");
LockSupport.unpark(Thread.currentThread());
System.out.println("current thread begin park");
LockSupport.park();
System.out.println("current thread end park");
}
}
输入后果如下:
以后线程调用 park 办法后并没有阻塞挂起,而是立刻返回了,因为此前调用 unpark 给了它许可证。
(3)void parkNanos(long nanos)
和 park 办法相似,但有个超时参数,如果期待了 nanos(单位纳秒)工夫后,以后线程没有被唤醒,会主动返回。
(4)void park(Object blocker)
以后线程调用 park(blocker)被挂起时,blocker 对象会被记录在以后线程外部,它的作用是当咱们应用诊断工具 (如 jstack 等)dump 线程时,能够察看到线程被阻塞在哪个类下面。诊断工具是通过 getBlocker() 拿到 blocker 对象的。因而 JDK 举荐咱们应用带有 blocker 参数的 park 办法,例如 LockSupport.park(this)。同样用代码来阐明下:
import java.util.concurrent.locks.LockSupport;
public class ParkWithBlockerDemo {public void parkWithBlocker() {LockSupport.park(this);
}
public static void main(String[] args) {ParkWithBlockerDemo demo = new ParkWithBlockerDemo();
demo.parkWithBlocker();}
}
程序运行后,线程会被阻塞挂起,此时先用 jps 命令找到它的 pid,而后用 jstack pid 执行,会有如下后果:
不加 blocker 参数的话,是不会有这个标记的,感兴趣的同学能够去试试。
park(blocker)办法源码:
public static void park(Object blocker) {Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
代码逻辑很清晰明了,原理就是真正 park 之前,将 blocker 对象绑定在以后 Thread 外部的某个变量中,等 park 返回后,解除绑定。其中 setBlocker 源码如下:
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
其中 parkBlockerOffset 是 Thread 类 parkBlocker 变量的内存偏移量。
private static final long parkBlockerOffset;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
(5)parkUntil(long deadline)
与 parkNanos 不同的是,deadline 是个相对工夫戳,示意到某个工夫点后,以后线程会被唤醒。
结束语
最初再来看个栗子来加深 LockSupport 的了解。
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class FIFOMutex {private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedDeque<>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
// 把以后线程放到队列开端
waiters.add(current);
// 只有队列头部的线程能力获取锁
while (waiters.peek() != current || locked.compareAndSet(false, true)) {LockSupport.park(this);
// 记录是否被中断过,并重置中断标记,这里其实就是疏忽中断
if (Thread.interrupted()) {wasInterrupted = true;}
}
// 移除队列头部元素,即胜利获取锁的以后线程
waiters.remove();
// 从新响应之前的中断
if (wasInterrupted) {current.interrupt();
}
}
public void unlock() {locked.set(false);
// 唤醒本来处于队列第二,当初处于头部的线程,让它尝试获取锁
LockSupport.unpark(waiters.peek());
}
}
这是一个先入先出的锁,也就是只有队列的头部线程能力获取锁,如果以后线程不是头部元素,或者锁曾经被其余线程获取,则调用 park 办法阻塞本人。
这里的 wasInterrupted 怎么了解呢?如果 park 办法是因为被中断而返回的,则疏忽中断,只记录 wasInterrupted=true。因为这阐明不是其余线程调用理解锁办法 unlock()外面的 unpark 让它返回的,它还不能获取锁,得持续阻塞。尽管以后线程对中断标记不感兴趣,但不代表其余线程也不敢趣味,所以在胜利获取锁后,从新对本人调用 interrupt,复原下中断标记的值。
参考资料:
《Java 并发编程之美》