
位于rt.jar包的java.util.concurrent.locks目录中, 主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。
如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。
public static void main(String[] args) {
System.out.println("begin park");
LockSupport.park();
System.out.println("end park");
}
直接在main函数里面调用park方法,最终只会输出begin park!,然后当前线程被挂起,这是因为在默认情况下调用线程是不持有许可证的。
在其他线程调用 unpark(Thread thread)方法并且将当前线程作为参数时,调用park方法而被阻塞的线程会返回。
另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。
当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread线程持有。
如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒。如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立刻返回。修改代码如下。
public static void main(String[] args) {
System.out.println("begin park");
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("end park");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("child thread begin to park");
LockSupport.park();
System.out.println("child thread unpark");
});
// 启动子线程
thread.start();
// 主线程休眠1s
TimeUnit.SECONDS.sleep(1);
// 调用unpark方法让thread持有许可证,然后park方法返回
System.out.println("main thread begin unpark ");
LockSupport.unpark(thread);
}
park方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足则还需要再次调用park方法。
例如,根据调用前后中断状态的对比就可以判断是不是因为被中断才返回的。
为了说明调用park方法后的线程被中断后会返回,我们修改上面的例子代码,删除LockSupport.unpark(thread);,然后添加thread.interrupt();,
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/12/5 8:53
* @mark: show me the code , change the world
*/
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("child thread begin to park");
// 调用park方法,挂起自己,只有被中断才推出循环
while(!Thread.currentThread().isInterrupted()){
LockSupport.park();
}
System.out.println("child thread unpark");
});
// 启动子线程
thread.start();
// 主线程休眠1s
TimeUnit.SECONDS.sleep(1);
// 中断子线程
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,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了。
public class TestPark {
public static void main(String[] args) {
TestPark testPark = new TestPark();
testPark.parkTest();
}
private void parkTest() {
// 调用park 挂起自己
LockSupport.park();
}
}运行代码后,使用jstack pid命令查看线程堆栈时可以看到如下输出结果。



使用带blocker参数的park方法,线程堆栈可以提供更多有关阻塞对象的信息。
来看下源码
public static void park(Object blocker) {
// 当前线程
Thread t = Thread.currentThread();
// 设置线程的blocker变量
setBlocker(t, blocker);
// 挂起线程
UNSAFE.park(false, 0L);
// 线程被激活后,清除blocker变量,因为一般都是在线程阻塞的时候才分析原因
setBlocker(t, null);
}Thread类里面有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的线程的成员变量里面。

相比park(Object blocker) 方法多了个超时时间。
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}其中参数deadline的时间单位为ms,该时间是从1970年到现在某一个时间点的毫秒值。这个方法和parkNanos(Object blocker, long nanos)方法的区别是,后者是从当前算等待nanos秒时间,而前者是指定一个时间点,比如需要等到2021.12.05日 12:00:00,则把这个时间点转换为从1970年到这个时间点的总毫秒数。
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/12/5 9:42
* @mark: show me the code , change the world
*/
public 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);
// 1 只有队首的线程可以获取锁
while(waiters.peek() != current || !locked.compareAndSet(false,true)){
LockSupport.park(this);
if (Thread.interrupted()) { // 2
wasInterrupted = true;
}
}
waiters.remove();
if (wasInterrupted){ // 3
current.interrupt();
}
}
public void unlock(){
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}这是一个先进先出的锁,也就是只有队列的首元素可以获取锁。