1、LockSupport是什么
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
- LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程。
- 3种让线程等待和唤醒的方式:
方式1:使用Object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程(可以理解为方式1和方式2的加强版)
2、waitNotify和awaitSignal的限制
- synchronized -> wait->notify实现线程之间的等待唤醒
import java.util.concurrent.locks.LockSupport;
// synchronized -> wait->notify
// lock -> await -> signal
public class LockSupportTest {
private static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t" + "coming in ....");
try {
objectLock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒了 ....");
}
},"thA").start();
new Thread(()->{
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t" + "唤醒其他线程 ....");
objectLock.notify();
}
},"thB").start();
}
}
输出结果:
thA coming in ....
thB 唤醒其他线程 ....
thA 被唤醒了 ....
如果让thA暂停三秒钟,让thB先获取锁objectLock,notify方法用来唤醒其他线程(实则没有任何线程等待), 再让thA获取锁,wait方法让thA线程等待,而thA不被唤醒,会一直处于等待/阻塞状态,即"thA 被唤醒了 …”一直不会输出。
- 调用顺序要先wait,在notify才能正常地实现线程之间的通知和唤醒。

还需要注意的是: - wait和notify方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。

- 实现线程的等待和唤醒,Condition接口中的await和signal方法,同前面使用Object类中的wait和notify方法来实现,其效果大致相同。
- 都必须要先等待(wait/await),再唤醒(notify/signal);等待和唤醒方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException
private static Condition condition = lock.newCondition();
private static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 当前线程睡眠三秒钟,让thB先执行
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "coming in ....");
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒了 ....");
} catch (RuntimeException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}, "thA").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "唤醒其他线程 ....");
} finally {
lock.unlock();
}
}, "thB").start();
}
使用LockSupport可以解决如上两个问题,也会使代码更为简练。
3、LockSupport原理分析
- LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。
- LockSupport分别使用park、unpark实现等待和唤醒,见官网原话:
Basic thread blocking primitives for creating locks and other synchronization classes.
This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.) link
其解读如下:
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
- LockSupport类使用了Permit(许可证)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。
- 许可看成是一种(0和1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1
- 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作,此外,park()+park(Object blocker) 可以用来阻塞当前线程阻塞传入的具体线程
LockSupport 源码摘要如下:
public class LockSupport {
// ...
public static void park() {
UNSAFE.park(false, 0L);
}
// ...
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
}
- park和unpark方法都需要调用Unsafe类中native修饰的方法,底层交由C语言去实现
- permit默认是0,调用park()方法,当前线程th1就会阻塞,直到别的线程th2调用unpark(Thread thread),将当前线程th1的permit设置为1时(表示当前线程获取到许可),th1会被th2唤醒,然后会将permit再次设置为0并返回。
- 调用unpark(thread)方法后,就会将th1线程的许可permit设置成1(注意多次调用unpark方法,不会累加,pemit值还是1),使得th1线程被唤醒。
4、LockSupport使用案例
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
// 当前线程睡眠三秒钟,让thB先执行
//try {
// TimeUnit.SECONDS.sleep(3);
//} catch (InterruptedException e) {
// throw new RuntimeException(e);
//}
System.out.println(Thread.currentThread().getName() + "\t" + "coming in ....");
LockSupport.park();
/* 如果这里有两个LockSupport.park(),由于permit的值为1,
上一行已经使用了permit,所以下一行被注释的打开会导致程序处于一直等待 的状态 * */
//LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒了 ....");
}, "thA");
threadA.start();
Thread threadB = new Thread(() -> {
//try {
// TimeUnit.SECONDS.sleep(1);
//} catch (InterruptedException e) {
// throw new RuntimeException(e);
//}
//如果有两个LockSupport.unpark(t1),由于permit的值最大为1,所以只能给park一个通行证
// LockSupport.unpark(threadA);
LockSupport.unpark(threadA);
System.out.println(Thread.currentThread().getName() + "\t" + "唤醒其他线程 ....");
}, "thB");
threadB.start();
}
运行如上程序,即使不加锁,也不会出现异常 IllegalMonitorStateException,也不需要关心线程之间等待和唤醒的先后顺序。
5、总结
- 没有使用LockSupport的等待唤醒通知机制必须synchronized里面执行wait和notify,在lock里面执行await和signal,这上面这两个都必须要持有锁才能干
- LockSupport:俗称锁中断,LockSupport 解决了 synchronized 和 lock 的痛点
- LockSupport不用持有锁块,不用加锁,程序性能好,无须注意唤醒和阻塞的先后顺序,不容易导致卡死
形象理解LockSupport:
- 线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
- 当调用park方法时:
如果有凭证,则会直接消耗掉这个凭证然后正常退出
如果无凭证,就必须阻塞等待凭证可用 - 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无放。
6、LockSupport面试题
- 为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
- 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1(不能累加),连续调用两次 unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

本文介绍了LockSupport类在Java并发编程中的关键作用,包括park()和unpark()方法的工作原理,以及它们与synchronized、wait/notify和Condition接口的对比。通过实例展示了如何避免常见问题,并强调了LockSupport在简化代码和提高性能方面的优势。
1730

被折叠的 条评论
为什么被折叠?



