LockSupport
为什么要学习LockSupport
java--JVM
JUC--AQS(前置知识:可重入锁,LockSupport)
LockSupport是什么?
一句话解释:是线程等待唤醒机制(wait/notify)的增强版本
LockSupport中的park()
和unpark()
的作用分别是阻塞线程和解除阻塞线程
3.0 LockSupport有哪些优势
三种线程等待和唤醒的方法
- 使用
Object
中的wait()
--notify()
方法来阻塞、唤醒线程 - 使用JUC包中
Condition
的await()
--signal()
方法来阻塞、唤醒线程 -
LockSupport
类的park()
--unpark()
可以阻塞当前线程、以及唤醒指定的被阻塞线程
1. 使用Object
中的wait()
--notify()
方法来阻塞、唤醒线程
wait方法和notify方法两个都不存在于同步代码块时会报错IllegalMonitorStateExxception
结论:
- Object类中的wait、notify用于线程的等待和唤醒时,都必须在synchronized内部执行(需要用到关键字synchronized)
- 只有先wait(),后notify()才能被正确唤醒,顺序颠倒则无法唤醒。
2. 使用JUC包中Condition
的await()
--signal()
方法来阻塞、唤醒线程
与Object类中的wait、notify类似一样有两个约束:
- 线程先要获得并持有锁,必须在锁块中
- 必须要先等待,后唤醒,才能被唤醒
3. LockSupport
类的park()
--unpark()
可以阻塞当前线程、以及唤醒指定的被阻塞线程
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每一个线程都有一个对应的许可(Permit)
Permit只有两个值1和0,其默认是0
可以把许可看成一种(0,1)的信号量(Semaphore),但是与Semaphore不同的是,许可的累加上限为1。
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park()方法才会被唤醒,然后再将permit设置为0返回
与之前的阻塞唤醒不同的是:
- 无锁块要求
- 额外支持先唤醒后等待
代码说明
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
static Object objectLock = new Object();
public static void main(String[] args) {
Thread a = new Thread(() -> {
try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"\t"+"----------conme in");
LockSupport.park();//等待通知放行,需要许可证
System.out.println(Thread.currentThread().getName()+"\t"+"----------线程被唤醒");
}, "A");
a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a);//为a线程发放了许可证
System.out.println(Thread.currentThread().getName()+"\t"+"----------通知动作");
}, "B");
b.start();
}
public static void waitNotify(){
new Thread(()->{
synchronized (objectLock){
try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"\t"+"----------conme in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"----------线程被唤醒");
}
},"A").start();
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t"+"----------通知动作");
}
},"B").start();
}
}
重点说明
- LockSupport是一个线程阻塞的工具类,所有方法都是静态方法
- LockSupport和每一个使用它的线程都有一个许可证相关联,这个许可证是不可以累积的,只有许可和非许可的区别,不会出现可以许可两次这种情况。
- 底层使用Unsafe类
例题
1. 为什么可以先唤醒后阻塞线程
因为LockSupport和每一个使用它的线程都有一个许可证相关联,使用unpark()唤醒时许可证被被设定为许可,之后调用阻塞park()时可以直接消费这个许可证(重置为0),故同样不会阻塞。
2. 为什么唤醒两次,阻塞两次,仍会有线程被阻塞住
因为LockSupport的许可证是不可以累积的,只有许可和非许可的区别,不会出现可以许可两次的情况。在第一次阻塞时就消费掉了这张许可证,第二次park()时则会阻塞。