LockSupport
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()和unpark()的作用分别是阻塞线程和解除阻塞线程。
3种让线程等待和唤醒的方法
- 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
- 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
// 正常情况下
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t"+"----come 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();
}
//输出结果
A ----come in
B ----唤醒动作
A ----被唤醒
//异常情况1:wait()和notify()脱离synchronized单独使用。
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(()->{
// synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t"+"----come 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();
}
//输出结果
java.lang.IllegalMonitorStateException
//异常情况2:先notify(),后wait(),A线程一直处于等待状态。
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(()->{
//暂停几秒钟,确保B线程先进入。
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t"+"----come 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();
}
//输出结果
B ----唤醒动作
A ----come in
结论:
- Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在sychronized内部执行(必须用到关键字sychronized),且成对出现使用
- 先wait,再notify才可以。
2. 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
//正常情况下
static Object objectLock = new Object();
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"----被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
},"A").start();
new Thread(()->{
lock.lock();
try{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t"+"----唤醒动作");
}finally{
lock.unlock();
}
},"B").start();
}
//输出情况
A ----come in
B ----唤醒动作
A ----被唤醒
//异常情况1:注销lock()和unlock()
static Object objectLock = new Object();
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(()->{
// lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"----被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// lock.unlock();
}
},"A").start();
new Thread(()->{
// lock.lock();
try{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t"+"----唤醒动作");
}finally{
// lock.unlock();
}
},"B").start();
}
//输出结果报异常
java.lang.IllegalMonitorStateException
//异常情况2:线程B先进入,即先signal(),后await()
static Object objectLock = new Object();
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"----被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
},"A").start();
new Thread(()->{
lock.lock();
try{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t"+"----唤醒动作");
}finally{
lock.unlock();
}
},"B").start();
}
//输出结果
B ----唤醒动作
A ----come in
传统的synchronized和Lock实现等待唤醒通知的约束:
- 线程需要获得并持有锁,必须在锁块(synchronized或lock中)
- 必须要先等待后唤醒,线程才能够被唤醒。
3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
//不需要在锁块中
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(() -> {
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + "\t" + "----唤醒动作");
}, "B");
b.start();
}
//输出结果
A ----come in
B ----唤醒动作
A ----被唤醒
//先执行unpark(),依然可以被唤醒
public static void main(String[] args) {
Thread a = new Thread(() -> {
try {
Thread.sleep(3000);
} 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是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根揭底,LockSupport调用的是Unsafe中的native代码。
- LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)。
- permit只有两个值1和0,默认是0。
- 可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。
- permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park()方法会被唤醒,然后会将permit再次设置为0并返回。
4. 面试题
为什么可以先唤醒线程后阻塞线程
-----因为unpark获取了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
-----因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。