目录
1.LockSupprt是什么?
1)LockSupport是用来创建和其他同步类的基本线程阻塞原语。
2)LockSupport类使用了一种名为permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)。
3)但与Semaphore不同的是,许可的累加上限是1。
2.LockSupport类的主要方法
park()/park(Object blocker)方法:阻塞当前线程/阻塞传入的具体线程。permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程发放permit,park方法才会被唤醒。
unpark(Thread thread)方法:唤醒处于阻塞状态的指定线程。调用unpark(thread)方法后,就会将thread线程的许可证permit发放,即之前阻塞中的LockSupport.park()方法会立即返回。
3.三种让线程等待和唤醒的方法
方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
代码如下:
public static void synchronizedNotify() throws InterruptedException {
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName()+"----come in");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----被唤醒");
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()-> {
synchronized (o) {
o.notify();
System.out.println(Thread.currentThread().getName() + "----发出通知");
}
},"t2").start();
}
结果:
结论:
1)wait方法和notify方法,都必须在同步方法或同步代码块中,才能正常执行,否则抛出IllegalMonitorStateException异常;
2)notify方法必须在wait方法后面执行,才能唤醒使用同一个对象(同一把锁)的wait方法进行阻塞的一个线程,否则无法唤醒对应的线程。
方式2: 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
代码如下:
public static void lockCondition() throws InterruptedException {
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"----come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"----被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()-> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----发出通知");
condition.signal();
}finally {
lock.unlock();
}
},"t2").start();
}
结果:
结论:
1)同一个lock对象创建的Condition对象的await方法和signal方法,必须是在同一个lock对象的lock方法和unlock方法对里面,才能正常执行,否则抛出IllegalMonitorStateException异常;
2)signal方法必须在await方法后面执行,才能唤醒用同一个lock对象创建的Condition对象的await方法进行阻塞的一个线程,否则无法唤醒对应的线程。
方式3: LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
代码如下:
public static void lockSupportPark() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "----被唤醒");
}, "t1");
t1.start();
// TimeUnit.SECONDS.sleep(1);
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "----发出通知");
LockSupport.unpark(t1);
},"t2").start();
}
结果:
结论:
1)LockSupport类的park方法和unpark方法要成对使用,无锁块要求。
2)LockSupport类的park方法和unpark方法执行不讲究先后顺序,先执行unpark方法许可证加1,后面调用park方法直接消耗一次许可证,直接放行。
3)同时多次调用unpark方法也只能放行一次,因为许可的累加上限是1。
4.重点说明
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport 是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程。
LockSupport 和每个使用它的线程都有一个许可(permit)关联。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark 也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用 park方法时
如果有凭证,则会直接消耗掉这个凭证然后正常退出;
如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
5.面试题
为什么可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够,不能放行。
6.官方说明