LockSupport 简介
LockSupport 是 Java 并发编程中一个非常重要的组件,咱们熟知的并发组件 Lock、线程池、CountDownLatch 等都是基于 AQS 完成的,而 AQS 内部操控线程堵塞和唤醒又是经过 LockSupport 来完成的。
从该类的注释上也能够发现,它是一个操控线程堵塞和唤醒的东西,与以往的不同是它处理了从前 wait()、notify()、await()、signal() 的局限。
回忆 synchronized 和 Lock
咱们知道 Java 中完成并发安全通常会经过这两种加锁的办法,对于 synchronized 加锁的办法,如果咱们想要操控线程的堵塞和唤醒是经过锁目标的 wait() 和 notify() 办法,以下面循环交替打印 AB 为例
int status = 2;
public static void main(String[] args) throws InterruptedException {
TestSync obj = new TestSync();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 1){
obj.wait();
}
System.out.println("A");
obj.status = 1;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 2){
obj.wait();
}
System.out.println("B");
obj.status = 2;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
}
如果咱们运用 Lock 完成类,上述代码几乎是相同的,仅仅先获取 Condition 目标
Condition condition = lock.newCondition();
把 obj.wait() 换成 condition.await(), obj.notify() 换成 condition.signal() 即可。
LockSupport 和 synchronized 和 Lock 的堵塞办法比照
| 技能 | 堵塞唤醒办法 | 局限 |
|---|---|---|
| synchronized | 运用锁目标的 wait()、notify() | 1. 只能用在 synchronized 包裹的同步代码块中 2. 必须先 wait() 才能 notify() |
| Lock | 运用 condition 的 await()、signal() | 1. 只能用在 lock 锁住的代码块中 2. 必须先 await() 才能 signal() |
| LockSupport | park()、unpark(Thread t) | 没有约束 |
LockSupport 的运用
下面代码中,咱们运用 LockSupport 去堵塞和唤醒线程,咱们能够多次测验,LockSupport 的 park() 和 unpark() 办法没有先后顺序的约束,也不需要捕获反常,也没有约束要在什么代码块中才能运用。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("A");
LockSupport.park();
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
LockSupport.unpark(t1);
}).start();
}
LockSupport 注意事项
许可证提前发放
从该类的注释中咱们能够看到这个类存储了运用它的线程的一个许可证,当调用 park() 办法的时分会判别当前线程的许可证是否存在,如果存在将直接放行,否则就堵塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();//不会堵塞
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先调用 unpark()");
LockSupport.unpark(t1);
},"t2").start();
}
看这个代码示例,这儿咱们在 t2 中先让线程 t1 unpark(), 然后在 t1 中调用 park(), 结果并不会堵塞 t1 线程。因为在 t2 中调用 LockSupport.unpark(t1); 的时分相当于给 t1 提前准备好了许可证。
许可证不会累计
LockSupport.unpark(t1); 不管调用多少次,t1 的通行证只有一个,当在 t1 中调用两次 park() 办法时线程依然会被堵塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();
LockSupport.park();
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先调用 unpark()");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
},"t2").start();
}
以上述代码为例,t1 将被堵塞。
LockSupport 底层完成
观察源码发现 park() 和 unpark() 最底下调用的是 native() 办法,源码在 C++ 中完成
@IntrinsicCandidate
public native void park(boolean isAbsolute, long time);
@IntrinsicCandidate
public native void unpark(Object thread);
对,这仅仅个标题,卷不动了,不去看 C/C++ 了。。。。
结语
LockSupport 是 Java 并发编程中非常重要的组件,这是咱们下一步阅览 AQS(AbstractQueuedSynchronizer) 源码的基础。总归咱们只要记住它是操控线程堵塞和唤醒的东西,并且知道它与其他堵塞唤醒办法的区别即可。
