简单介绍

LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,是线程等待唤醒机制的一种实现工具类。

等待唤醒机制

等待唤醒机制 是线程中的一种协作机制。多线程之间不单有竞争锁的情况,还有相互协作的场景。比如线程A执行完某一操作需要挂起一段时间,将运行的机会让给线程B,当线程B执行完任务后就唤醒线程A继续做任务。

Java 提供了 3 种等待唤醒机制:

方式等待唤醒执行要求
Objectwait 方法notify 方法(随机唤醒)①需要获取锁,否则会报错
②需先调用 wait 才能调用 notify,否则线程会一直等待
Conditionawait 方法signal 方法(随机唤醒)①需要获取锁,否则会报错
②需先调用 await 才能调用 signal,否则线程会一直等待
LockSupportpark 方法unpark 方法(指定唤醒)

其中,LockSupport 类提供的都是静态的方法且对执行没有要求,这让线程可以在任意位置实现等待或唤醒,非常方便。

实现原理

LockSupport 类使用 Permit(许可) 概念来做到等待和唤醒线程的功能:

1
每个使用 LockSupport 的线程都有一个 Permit,它相当于通行开关(0:等待 1: 通行),默认值为 0,最大值为 1。

当线程调用 park() 方法时:

  • 有许可,即 Permit 值为 1 时,会将 Permit 改成 0,同时正常退出方法。
  • 无许可,即 Permit 值为 0 时,线程会被挂起(线程状态变成 WAIT)。

当线程调用 unpark(thread) 方法时:

  • 指定线程的 Permit 值会变成 1(多次 unpark()Permit 值也是 1),如果指定的线程处在 WAIT 状态,则会被唤醒。

线程调用 park() 方法是否被挂起只与它的许可值相关,因此,如果其他线程先调用 unpark(t1) 方法,t1 线程再调用 park() 是不会被挂起的。

案例演示

  • 案例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LockSupportTest {

public static void main(String[] args) throws Exception {

Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始等待");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " 唤醒成功");
}, "t1");

t1.start();

TimeUnit.SECONDS.sleep(1);

System.out.println(Thread.currentThread().getName() + " 开始唤醒 t1");
LockSupport.unpark(t1);
}
}

运行结果:

1
2
3
t1 开始等待
main 开始唤醒 t1
t1 唤醒成功

从结果可以看出,t1 线程先调用 park() 方法后被挂起等待,1 秒后主线程调用 unpark(t1),最后 t1 被唤醒执行 System.out 输出后结束线程。

  • 案例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LockSupportTest {

public static void main(String[] args) {

Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(LocalDateTime.now() + " -> " + Thread.currentThread().getName() + " 睡眠了 1 秒");
LockSupport.park();
System.out.println(LocalDateTime.now() + " -> " + Thread.currentThread().getName() + " 结束");
} catch (Exception e) {
e.printStackTrace();
}
}, "t1");

t1.start();

System.out.println(LocalDateTime.now() + " -> " + Thread.currentThread().getName() + " 提前开始唤醒 t1");
LockSupport.unpark(t1);
}
}

运行结果:

1
2
3
2023-02-21T10:47:11.774 -> main 提前开始唤醒 t1
2023-02-21T10:47:12.755 -> t1 睡眠了 1 秒
2023-02-21T10:47:12.755 -> t1 结束

主线程先调用 unpark(t1) 方法,1 秒后 t1 线程再调用 park() 并没有被挂起等待,而是随后立即执行了 System.out 的输出结束线程。

小结

  • LockSupport 通过 Permit 实现等待唤醒机制。
  • LockSupport 通过 park() 挂起线程(Permit 值变成 0),通过 unpark(thread) 唤醒线程(Permit 值变成 1)。
  • 使用 LockSupport 的线程都有一个 Permit 且最大值为 1,即无论调用多少次 unpark(thread),最终 Permit 值都是 1。