1.LockSupport是什么?
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
- LockSupport中的park()和unPark()的作用分别是阻塞线程和解除阻塞线程
2.线程等待唤醒机制(wait/notify)
- 1.三种让线程等待和唤醒的方法
- 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
- 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程。
- 方式3:LockSupport类可以阻塞当前线程以唤醒指定被阻塞的线程。
- 2.Object类中的wait和notify方法实现线程等待和唤醒
- 问题1:Object类中的wait,notify,notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。
- 问题2:将notify放在wait方法前面,程序无法执行,无法唤醒。
- 3.Condition接口中的await后signal方法实现线程的等待和唤醒
会出现同Object类中的wait和notify方法实现线程等待和唤醒一样的两个问题。 - 4.传统的synchronized和Lock实现等待唤醒通知的约束。
线程先要获得并持有锁,必须在锁块(synchronized或lock)中。
必须要先等待后唤醒,线程才能够被唤醒。 - 5.LockSupport类中的park等待和unpark唤醒
是什么:通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作。
官网解释:LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和0,默认为0。可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。
方法调用:
- 阻塞:park()/park(Object blocker),阻塞当前线程/阻塞传入的具体线程。
调用LockSupport.park()时
public static void(){
UNSAFE.park(false,0L);
}
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回。
- 唤醒:unpark(Thread thread),唤醒处于阻塞状态的指定线程。
调用LockSupport.unpark(thread)时
public static void unpark(Thread thread){
if(thread !=null)
UNSAFE.unpark(thread);
}
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
代码:
正常+无锁块要求
之前错误的先唤醒后等待LockSupport照样支持
public class LockSupportDemo {
public static void main(String[] args) {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t 唤醒");
}, "a");
a.start();
Thread b = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + "\t 通知了");
}, "b");
b.start();
}
}
运行结果:
a come in
b 通知了
a 唤醒
public class LockSupportDemo {
public static void main(String[] args) {
Thread a = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t 唤醒");
}, "a");
a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + "\t 通知了");
}, "b");
b.start();
}
}
运行结果:
b 通知了
a come in
a 唤醒
原理:
归根结底,LockSupport调用的Unsafe中的native代码
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park就会消费permit,也就是将1变成0,同时park立即返回。如果再次调用park会变成阻塞(因为permit为0了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会累积凭证。形象的理解,线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时,如果有凭证,则会直接消耗掉这个凭证然后正常退出,如果无凭证,就必须阻塞等待凭证可用。而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。