Java LockSupport
简介
查看JavaDoc
阅读以上内容了解到LockSupport
引入一个“许可证”的概念,并且每个使用LockSupport
的每个线程都会关联一个“许可证”
这个“许可证”类似“锁计数器”但是与“锁计数器”不同,“许可证”不可累积,最多只有一个。言外之意就是“许可证”只能有两个值“0”和“1”。
知道两个关键的方法LockSupport#park()
和LockSupport#unpark()
,这两个方法是对线程等待唤醒(wait/notify
)机制的加强。
加强在哪?
要知道加强在哪首先要复习下synchronized
与Lock
的线程等待与唤醒使用方法。
synchronized
使用java.lang.Object#wait()
/java.lang.Object#notify
来实现线程的等待与唤醒。
- 使用
wait
与notify
必须在synchronized
里使用同一个锁对象,若移除同步代码块(synchronized
)则会出现IllegalMonitorStateException
异常。 wait
与notify
执行顺序必须先wait
后notify
,否则等待钟的线程将无法唤醒。
Lock
使用Condition
的Condition#await()
与Condition#signal
方法来实现线程的等待与唤醒。
- 使用
Lock#newCondition
获得Condition
对象 - 使用
await
与signal
必须在同一锁对象中使用 await
与signal
执行顺序必须先await
后signal
,否则等待钟的线程将无法唤醒。
小结
传统的synchronized
与Lock
实现等待唤醒的约束:
- 线程先要获得并持有锁,必须在锁块(
synchronized
或Lock
)中。 - 必须要先等待后唤醒,线程才能够被唤醒。
LockSupport
LockSupport
类使用了一种名为“Permit”(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个“Permit”,并且只有两个值“0”和“1”,默认为“0”
可以把“Permit”看作一种(0,1)信号量(Semaphore),但与“Semaphore”不同的是“Permit”的累加上限是1。
park(等待)
除非许可证可用,否则禁用当前线程以进行线程调度。
unpark(唤醒)
如果给定线程尚不可用,则为其提供许可。
实战
final Thread lockSupportThread1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t in");
System.out.println(Thread.currentThread().getName() + "\t do park");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t done");
}, "lockSupportThread1");
lockSupportThread1.start();
// 保证线程执行顺序,主线程等待1s
TimeUnit.SECONDS.sleep(1);
final Thread lockSupportThread2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t in");
System.out.println(Thread.currentThread().getName() + "\t do unpark");
LockSupport.unpark(lockSupportThread1);
System.out.println(Thread.currentThread().getName() + "\t done");
}, "lockSupportThread2");
lockSupportThread2.start();
控制台输出:
lockSupportThread1 in
lockSupportThread1 do park
lockSupportThread2 in
lockSupportThread2 do unpark
lockSupportThread2 done
lockSupportThread1 done
总结
LockSupport
提供park
和unpark
方法实现线程的阻塞和解除线程阻塞。- 每个使用
LockSupport
的线程都有一个“凭证”,值只能为“0”或“1”,默认为“0”。 - 调用一次
unpark
会增加一次“凭证”的值,只能由“0”变为“1”,调用一次park
则会消费“凭证”,多次调用unpark
“凭证”的值也只会为“1”。 park
与unpark
没有区分调用顺序,只需要注意“凭证”的值。- 不需要锁块