LockSupport
理论
LockSupport是什么?
LockSupport是Java.util.concurrent.locks包下的一个类。是线程等待唤醒进制的(wait和notify)的改良版。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport的park() 和unpark()作用分别是阻塞线程(相当于Object 的wait())和解除阻塞线程(相当于Object 的notify())。
在学习LockSupport之前,我们知道2种让线程等待和唤醒的方法
- Object类的wait和notify方法实现线程的等待和唤醒
- Condition接口的await()方法和signal()方法实现线程的等待和唤醒。
而通过LockSupport的park() 和unpark()方法也可以让线程等待和唤醒。
在讲LockSupport之前,有必要知道为什么要引入,也就是LockSupport做到了Object和Condition的没有做到的什么地方?
wait和notify的异常情况
要求:实现线程A等待之后, B线程唤醒A线程继续工作。
package LockSup;
/**
* @author Johnny Lin
* @date 2021/6/13 14:25
*
*
*/
public class NotifyWaitDemo {
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 {
System.out.println(Thread.currentThread().getName() + "\t wait……");
objectLock.wait();
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t come in");
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t 通知");
}
},"B").start();
}
}
正常执行结果:
异常情况1
可是如果将wait和notify所在代码块的synchronized关键字都注释掉,会怎么样呢?
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
System.out.println(Thread.currentThread().getName() + "\t wait……");
objectLock.wait();
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t 通知");
},"B").start();
执行结果
异常情况2
如果是B线程先拿到锁然后执行唤醒,后A再拿到锁进入等待状态会怎么样?
new Thread(()->{
//保证B线程先执行拿到锁
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
System.out.println(Thread.currentThread().getName() + "\t wait……");
objectLock.wait();
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t come in");
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t 通知");
}
},"B").start();
结论: Obejct类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行,且必须要用到关键字synchronized!!
await和signal的异常情况
package LockSup;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Johnny Lin
* @date 2021/6/13 14:56
*/
public class SignalAwaitDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(()->{
lock.lock();
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
System.out.println(Thread.currentThread().getName() + "\t wait……");
try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} finally {
lock.unlock();
}
},"A").start();
//让A线程先执行
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
lock.lock();
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t 通知");
} finally {
lock.unlock();
}
},"B").start();
}
}
执行结果
异常情况1
如果把lock()和unlock都注释掉会怎么样?
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
System.out.println(Thread.currentThread().getName() + "\t wait……");
try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} finally {
}
},"A").start();
//让A线程先执行
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t 通知");
} finally {
}
},"B").start();
执行结果:
异常情况2
new Thread(()->{
//A线程休眠2秒 保证B线程先执行
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
lock.lock();
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
System.out.println(Thread.currentThread().getName() + "\t wait……");
try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
} finally {
lock.unlock();
}
},"A").start();
new Thread(()->{
lock.lock();
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t 通知");
} finally {
lock.unlock();
}
},"B").start();
总结传统的synchronized和Lock实现等待唤醒通知的约束:
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中。
- 必须先等待后唤醒,线程才能被唤醒。
代码
在 了解了以前Object和Condition的劣处之后自然而然的就引出了LockSupport。
LockSupport类引入一个许可(permit)的概念来做到阻塞和唤醒线程的功能,每个线程都要一个permit。
permit只有两个值1和0,默认是0。permit与semaphore类似,不同的是,permit的累加上限是1 。
官方解释:
park()方法
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程通过unpark将当前线程的permit设置为1时,park方法才会被唤醒。然后将permit再次设置为0并返回。
LockSupport类
public static void park() { U.park(false, 0L); }
调用Unsafe类的park方法
@HotSpotIntrinsicCandidate
public native void park(boolean isAbsolute, long time);
unpark()方法
调用unpark(thread)方法只有,就会将thread线程的许可permit设置成1,会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
注意: 多次调用unpark方法,不会累加。permit值还是1
LockSupport的unpark()方法
public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
调用Unsafe类的unpark(Object thread)
@HotSpotIntrinsicCandidate
public native void unpark(Object thread);
package LockSup;
import java.util.concurrent.locks.LockSupport;
/**
* @author Johnny Lin
* @date 2021/6/13 15:38
*/
public class LockSupportDemo {
public static void main(String[] args) {
Thread A = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
System.out.println(Thread.currentThread().getName() + "\t wait……");
//阻塞
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
},"A");
Thread B = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
System.out.println(Thread.currentThread().getName() + "\t 通知");
LockSupport.unpark(A);
}, "B");
A.start();
B.start();
}
}
执行结果
Thread A = new Thread(()->{
//A 线程休眠让B线程先执行 发放通行证
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t come in");
System.out.println(Thread.currentThread().getName() + "\t wait……");
//阻塞
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
},"A");
A.start();
Thread B = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
System.out.println(Thread.currentThread().getName() + "\t 通知");
//通知唤醒A线程
LockSupport.unpark(A);
LockSupport.unpark(A);
}, "B");
B.start();
执行结果
可以看到,即使是B先执行unpark(),A线程park()阻塞后也能正常被 唤醒。相当于B线程预先给A发放了通行证。
总结
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。实际上,LockSupport调用Unsafe类中的native代码。
线程阻塞需要消耗凭证permit,这个凭证最多只有1个。当调用park方法时:
- 如果有凭证,则会直接消耗这个凭证然后正常退出。
- 如果无凭证,则必须阻塞等待凭证可用。
当调用unpark方法时,则会增加一个凭证,但凭证最多只能有1个,累加无效。