什么是LockSupport
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类中的park()方法可以让当前线程阻塞,直到被唤醒或被中断。unpark()方法可以唤醒一个被阻塞的线程。
LockSupport的底层实现依赖于Java虚拟机中的Unsafe类中的native代码,相对java.util.concurrent.locks.Lock接口更加底层和高效。LockSupport的使用场景包括创建锁和其他同步类、实现等待/通知机制、实现锁的公平性等。
线程的等待唤醒机制
三种让线程等待/唤醒的方法:
- 使用 Object 类中的 wait() 方法让线程等待,notify() 方法唤醒线程
- 使用 ava.util.concurrent.locks.Condition 接口中 await() 方法让线程等待, signal() 方法唤醒线程
使用 LockSupport 类中的 park() 方法让线程等待,unpark() 方法唤醒线程
使用 Object 类中的 wait() 和 nofity() 实现线程的等待和唤醒
java
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
LockSupportDemo supportDemo = new LockSupportDemo();
new Thread(supportDemo::threadWait,"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(supportDemo::threadWake,"t2").start();
}
/**
* 阻塞
*/
public void threadWait(){
synchronized (this){
try {
System.out.println("线程:"+Thread.currentThread().getName()+" 被阻塞");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+" 被唤醒");
}
}
/**
* 唤醒
*/
public void threadWake(){
synchronized (this){
System.out.println("线程:"+Thread.currentThread().getName()+" 准备唤醒 t1");
this.notify();
System.out.println("线程:"+Thread.currentThread().getName()+" 完成唤醒 t1");
}
}
}
执行结果
线程:t1 被阻塞
线程:t2 准备唤醒 t1
线程:t2 完成唤醒 t1
线程:t1 被唤醒
使用 Condition 接口中的 await() 和 signal() 实现线程的等待和唤醒
java
public class LockSupportDemo {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
LockSupportDemo supportDemo = new LockSupportDemo();
new Thread(supportDemo::threadWait,"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(supportDemo::threadWake,"t2").start();
}
/**
* 阻塞
*/
public void threadWait(){
lock.lock();
System.out.println("线程:"+Thread.currentThread().getName()+" 被阻塞");
try {
condition.await();
System.out.println("线程:"+Thread.currentThread().getName()+" 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 唤醒
*/
public void threadWake(){
lock.lock();
try {
System.out.println("线程:"+Thread.currentThread().getName()+" 准备唤醒 t1");
condition.signal();
System.out.println("线程:"+Thread.currentThread().getName()+" 完成唤醒 t1");
}finally {
lock.unlock();
}
}
}
线程:t1 被阻塞
线程:t2 准备唤醒 t1
线程:t2 完成唤醒 t1
线程:t1 被唤醒
上述两个对象Object和Condition使用的限制条件:
- 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
- 必须要先等待后唤醒,线程才能够被唤醒
LockSupport类中的park等待和unpark唤醒
LockSupport 这个类为每个使用它的线程关联一个许可(在java.util.concurrent.Semaphore Semaphore类的意义上),通过 Permit(许可) 的机制来做到 线程的阻塞和唤醒。每个线程都有一个许可(Permit) ,许可证只能有一个,累加上限是1
(这个和Semaphore不同)。
LockSupport 主要方法:
- park/park(Object blocker)-------阻塞当前线程/阻塞传入的具体线程
- permit(许可) 默认是没有,不能放行,所以一开始调用了park()方法会阻塞当前线程,当别的线程调用了 unpark(当前线程) 方法将当前线程的 permit(许可) 发放 后才会唤醒当前线程,之前阻塞中的LockSupport.park()方法会立即返回。
- unpark(Thread thread) --------- 唤醒指定的处于阻塞状态的线程
java
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("调用 LockSupport.park() 方法,线程t1 被阻塞");
LockSupport.park();
System.out.println("发放了许可证,线程 t1 被唤醒");
});
t1.start();
new Thread(()->{
System.out.println("线程 t2 准备给 t1 发放许可证");
//此处指定要唤醒的线程
LockSupport.unpark(t1);
System.out.println("线程 t2 准备给 t1 许可证发放完成");
},"t2").start();
}
}
scss
执行结果
调用 LockSupport.park() 方法,线程t1 被阻塞
线程 t2 准备给 t1 发放许可证
线程 t2 准备给 t1 许可证发放完成
发放了许可证,线程 t1 被唤醒
小提示:使用LockSupport 不必一定要先 t1 线程先阻塞再让 t2 发放许可去唤醒,也可以是 t2 线程 先发放许可,然后 t1 线程持有许可,阻塞后被直接唤醒放行
代码演示:先发放许可,在阻塞时直接放行
csharp
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用 LockSupport.park() 方法,线程t1 被阻塞");
LockSupport.park();
System.out.println("发放了许可证,线程 t1 被唤醒");
});
t1.start();
/*
先发放许可证,也是可以实现 t1`阻塞后被唤醒的
*/
new Thread(()->{
System.out.println("线程 t2 准备给 t1 发放许可证");
LockSupport.unpark(t1);
System.out.println("线程 t2 准备给 t1 许可证发放完成");
},"t2").start();
}
}
scss
线程 t2 准备给 t1 发放许可证
线程 t2 准备给 t1 许可证发放完成
调用 LockSupport.park() 方法,线程t1 被阻塞
发放了许可证,线程 t1 被唤醒
注意!!!
线程阻塞后唤醒需要消耗凭证(permit),这个凭证最多只有一个
- 当带哦用park()时,如果有凭证,则会消耗掉这个凭证(permit)然后正常通行,如果没有凭证(permit),则必须阻塞等待凭证可用;
- 当调用 unpark时,它会增加一个凭证,但是凭证最多只有一个,累加是无效的,不论调用多少次unpark(),都只会有一个凭证
代码示例:
java
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("调用 LockSupport.park() 方法,线程t1 第一次被阻塞");
LockSupport.park();
System.out.println("调用 LockSupport.park() 方法,线程t1 第二次被阻塞");
//permit只有一个,第一次唤醒已经消耗完了,再次park被阻塞
LockSupport.park();
System.out.println("发放了许可证,线程 t1 被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
System.out.println("线程 t2 准备给 t1 发放许可证");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
System.out.println("线程 t2 准备给 t1 许可证发放完成");
},"t2").start();
}
}
执行结果:从控制台看到程序运行到 "调用 LockSupport.park() 方法,线程t1 第二次被阻塞"
再次被阻塞
总结
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport时调用Unsafe中的native代码
- LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport和每个使用它的线程都有一个许可(Peimit)关联,每个线程都有一个相关的permit,peimit最多只有一个,重复调用unpark也不会积累凭证
- LockSupport可以突破wait/notify的原有调用顺序:因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻
- 唤醒三次后阻塞两次,最终结果还会阻塞线程:permit(许可)的数量最多为1,连续调用三次unpark和调用一次unpark效果一样,只会增加一个permit(许可),而调用两次park却需要消费两个permit(许可),permit(许可)不够,不能放行