目录
一、关于LockSupport
什么是LockSupport?
官方Api文档的定义为:用于创建锁和其他同步类的基本线程阻塞原语。
乍一看,有些懵。我们通过文档发现,LockSupport类没有提供公共的构造器,意味着不能实例化。我们可以看一下它所提供的方法。其中最重要的就是park()和unpark()方法。它们的作用如下所示。
简单用一句话概括就是:LockSupport中park()和unpark()方法的作用分别是阻塞线程和解除阻塞线程。
LockSupport机制,其实是对线程等待唤醒机制的另一种优化和提升。
二、回顾线程等待唤醒机制
2.1 让线程等待和唤醒的三种方法
方法一
使用Object类中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
方法二
使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程。
方法一和方法二的注意点:
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
- wait()/notify()/notifyAll()三个方法都必须在同步方法或同步代码块中执行,三个方法的调用者也必须是当前的锁对象(即当前同步方法或同步代码块中的同步监视器)。否则会报IllegalMonitorStateException异常。
- 如果先notify()执行再执行wait()方法,等待的线程无法被唤醒。(因为没线程唤醒了)
- await()/signal()/signalAll()也一样。只是API不同,底层实现原理是一样的。必须在lock锁中执行,否则会报异常。也必须先等待后唤醒才能被唤醒。
方法三
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能。
主要使用LockSupport的park()和unpark(Thread thread)方法。
原理:permit许可证默认没有不能放行,所以一开始调用park()方法当前线程就会阻塞,直到别的线程给当前线程发放permit,park()方法才会被唤醒。
它相比之下的好处是:
不用在synchronized和lock中执行。
先唤醒锁,然后等待。这种情况下也能唤醒等待。此时的park()方法相当于等待不起作用。
问题:为什么可以先唤醒线程后阻塞线程?
因为unpark获取了一个凭证,之后再调用park方法就名正言顺的凭证消费。故不会阻塞线程。
问题:为什么唤醒两次后阻塞两次,但最终还是会阻塞线程?
因为凭证的数量最多是1.连续两次调用unpark和调用一次unpark效果是一样的,只会增加一个凭证。而调用两次park却需要消费两个凭证,凭证不够。不能放行。
2.2 代码演示
为了便于理解,这里针对以上的三种等待唤醒机制进行代码演示。
关于wait()和notify()
/**
* 代码解释:
* 一开始t1线程启动,然后被wait(),阻塞当前线程,使得线程停滞,同时释放了同步监视器
* 然后等待了几秒后,t2线程启动,t2线程notify()唤醒了当前被wait()的这个线程。
*/
public class LockSupportTest {
public static void main(String[] args) {
Object object = new Object();
new Thread(()->{
synchronized (object){
System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
}
},"t1").start();
// 暂停几秒
try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
synchronized (object){
object.notify();
System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
}
},"t2").start();
}
}
/**
* 代码解释:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则,会出现IllegalMonitorStateException异常
*/
public class LockSupportTest {
public static void main(String[] args) {
Object object = new Object();
new Thread(()->{
//synchronized (object){
System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
//}
},"t1").start();
// 暂停几秒
try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
//synchronized (object){
object.notify();
System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
//}
},"t2").start();
}
}
/**
* 代码解释:
* t1线程先进来,但是需要执行了几秒,还没有获取锁
* 然后紧随其后t2线程进来,唤醒了object锁对象,--> 实际上还当前锁对象还没被wait()
* 当我们t1线程执行几秒结束后,执行同步代码块的内容,
* 此时t2线程肯定执行完了代码,释放了锁,因此t1线程可以进入自己的代码块内。
* 但是t1线程接着需要执行object.wait();方法。
* 由于t2线程已经结束了,所以没法去唤醒t1线程,因此t1线程就一致阻塞着,等待唤醒。
*/
public class LockSupportTest {
public static void main(String[] args) {
Object object = new Object();
new Thread(()->{
// 暂停几秒
try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
synchronized (object){
System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
}
},"t1").start();
new Thread(()->{
synchronized (object){
object.notify();
System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
}
},"t2").start();
}
}
同理,await()和signal()
package com.hssy.jucstudy.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 代码解释:
* 同理,await()方法和signal()方法也是一样的道理。
*/
public class LockSupportDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
condition.await();
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1").start();
try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
} finally {
lock.unlock();
}
},"t2").start();
}
}
LockSupport的park()和unpark()
public class LockSupportStudy {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}, "t1");
t1.start();
try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
},"t2").start();
}
}
package com.hssy.jucstudy.juc;
import java.util.concurrent.locks.LockSupport;
/**
* 代码解释:
* t2线程先唤醒锁,也不会造成t1线程阻塞
*/
public class LockSupportStudy {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}, "t1");
t1.start();
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
},"t2").start();
}
}
package com.hssy.jucstudy.juc;
import java.util.concurrent.locks.LockSupport;
/**
* 代码解释:
* 许可证不会累计,最多只有1个
*/
public class LockSupportStudy {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}, "t1");
t1.start();
new Thread(()->{
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
},"t2").start();
}
}