LockSupport线程工具类 定义了许多方法来控制当前线程 俗称 锁中断
是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()和unpark()分别是阻塞线程和解除阻塞线程
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可, 许可只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
线程等待唤醒机制 3种让线程等待和唤醒的方法方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
Object wait notify notifyAll
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
Condition await signal signalAll
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
LockSupport park unpark
Object类中的wait和notify方法实现线程等待和唤醒package com.dongguo.locksupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(()->{
synchronized (objectLock){
try {
System.out.println(Thread.currentThread().getName()+"--阻塞");
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--被唤醒");
}
},"t1").start();
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
}
},"t2").start();
}
}
运行结果:
t1--阻塞
t2--发出唤醒通知
t1--被唤醒
异常一
Object类中的wait和notify方法只能在synchronized同步代码块或者同步方法中使用,且成对出现。不然会报异常IllegalMonitorStateException
package com.dongguo.locksupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(()->{
// synchronized (objectLock){
try {
System.out.println(Thread.currentThread().getName()+"--阻塞");
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--被唤醒");
// }
},"t1").start();
new Thread(()->{
// synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
// }
},"t2").start();
}
}
运行结果
t1--阻塞
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:24)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:15)
at java.lang.Thread.run(Thread.java:748)
异常二
wait()要在notify()之前,
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock){
try {
System.out.println(Thread.currentThread().getName()+"--阻塞");
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--被唤醒");
}
},"t1").start();
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
}
},"t2").start();
}
}
运行结果
t1阻塞等待被唤醒
Condition接口中的await后signal方法实现线程的等待和唤醒package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "--阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
运行结果
t1--阻塞
t2--发出唤醒通知
t1--被唤醒
异常一
lock、unlock里面才能正确调用condition中线程等待await和唤醒signal的方法
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
// lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "--阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1").start();
new Thread(() -> {
// lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
} finally {
// lock.unlock();
}
}, "t2").start();
}
}
运行结果
t1--阻塞
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:33)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at com.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:22)
at java.lang.Thread.run(Thread.java:748)
异常二
先await后signal顺序不能换
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Dongguo
* @date 2021/9/6 0006-15:40
* @description:
*/
public class LockSupportDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "--阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
运行结果
t1阻塞
Object和Condition使用的限制条件线程先要获得并持有锁,必须在锁块(synchronized或lock)中,并且wait/notify或await/signal成对出现
必须要先等待后唤醒,线程才能够被唤醒
加锁会被阻塞
LockSupport类中的park等待和unpark唤醒 主要方法 阻塞park() /park(Object blocker)
阻塞当前线程/阻塞传入的具体线程
一般使用park()
调用LockSupport.park()时
park()
/**
* 为了线程调度,在许可可用之前阻塞当前线程。
* 如果许可可用,则使用该许可,并且该调用立即返回;
* 否则,为线程调度禁用当前线程,并在发生以下三种情况之一以前,使其处于休眠状态:
* 1. 其他某个线程将当前线程作为目标调用 unpark
* 2. 其他某个线程中断当前线程
* 3. 该调用不合逻辑地(即毫无理由地)返回
*/
public static void park() {
UNSAFE.park(false, 0L);
}
park(Object blocker)
public static void park(Object blocker) {
//获取当前线程
Thread t = Thread.currentThread();
//记录当前线程阻塞的原因,底层就是unsafe.putObject,就是把对象存储起来
setBlocker(t, blocker);
//执行park
unsafe.park(false, 0L);
//线程恢复后,去掉阻塞原因
setBlocker(t, null);
}
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒, 然后会将permit再次设置为零并返回。
唤醒unpark(Thread thread)
唤醒处于阻塞状态的指定线程
LockSupport.unpark(thread);
/**
* 如果给定线程的许可尚不可用,则使其可用。
* 如果线程在 park 上受阻塞,则它将解除其阻塞状态。
* 否则,保证下一次调用 park 不会受阻塞。
* 如果给定线程尚未启动,则无法保证此操作有任何效果。
* @param thread: 要执行 unpark 操作的线程;该参数为 null 表示此操作没有任何效果。
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
从源码可以看到真正的实现均在 Unsafe.class中
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
与 Object 的 wait & notify (condition的await signal)相比
wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必 park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】 park & unpark 可以先 unpark,而 wait & notify 不能先 notify
代码package com.dongguo.locksupport;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-16:14
* @description:
*/
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "--阻塞");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1");
t1.start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
LockSupport.unpark(t1);
},"t2").start();
}
}
运行结果
t1--阻塞
t2--发出唤醒通知
t1--被唤醒
LockSupport解决了Object和Condition实现线程阻塞和唤醒
线程先要获得并持有锁,必须在锁块(synchronized或lock)中的问题
而且park和unpark顺序可以不固定
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-16:14
* @description:
*/
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--阻塞");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1");
t1.start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
LockSupport.unpark(t1);
},"t2").start();
}
}
运行结果
t2--发出唤醒通知
t1--阻塞
t1--被唤醒
有一点比较难理解的,是unpark操作可以再park操作之前。也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。
但是这个“许可”是不能叠加的,“许可”是一次性的。
比如线程t2连续调用了三次unpark函数,当线程t1调用park函数就使用掉这个“许可”,如果线程t1再次调用park,则进入等待状态。
先执行两个park()后,再unpark()
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-16:14
* @description:
*/
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "--阻塞");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1");
t1.start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
},"t2").start();
}
}
运行结果
解决使用两个线程,两次唤醒
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-16:14
* @description:
*/
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "--阻塞");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1");
t1.start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--t2发出唤醒通知");
LockSupport.unpark(t1);
},"t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--t3发出唤醒通知");
LockSupport.unpark(t1);
},"t3").start();
}
}
运行结果
t1--阻塞
t2--t2发出唤醒通知
t3--t3发出唤醒通知
t1--被唤醒
要确保unpark不是唤醒同一个park
先执行两个unpark()后,再park()
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-16:14
* @description:
*/
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--阻塞");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "--被唤醒");
}, "t1");
t1.start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
},"t2").start();
}
}
运行结果
先执行t2,执行两次unpark 将许可设置为1
LockSupport.unpark(t1); LockSupport.unpark(t1);
执行t1
第一次执行park LockSupport.park(); 消费许可
第二次执行park 许可为0,进入阻塞
LockSupport对应中断的响应性AQS (AbstractQueuedSynchronizer)底层就是调用park()方法,保证阻塞被唤醒后,如果加锁失败,可以再次阻塞,减轻资源消耗,在出现等待队列时,队列中的某个节点是可以响应中断的:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException。
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-20:49
* @description:
*/
public class LockSupportDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println( "park前中断标志位1:" + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"被唤醒 中断状态2"+ Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
运行结果
park前中断标志位1:false
t1被唤醒 中断状态2true
park()的线程被interrupt()后的状态和正常线程被interrupt()后的状态一样
park方法可以响应中断
所以park方法让线程等待后 有两种方法唤醒
1unpark
2interrupt
但是推荐unpark
如果打断标记已经是 true, 则 park 会失效
park()的线程被interrupt()后
再次park()就失效了 无法阻塞
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-20:49
* @description:
*/
public class LockSupportDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println( "park前中断标志位1:" + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"被唤醒 中断状态2"+ Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
LockSupport.unpark(t1);
}
}
运行结果
park前中断标志位1:false
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
for循环第一次park阻塞后被interrupt()唤醒
之后的循环park都无法将线程阻塞了。
可以使用 Thread.interrupted() 清除打断状态
package com.dongguo.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author Dongguo
* @date 2021/9/6 0006-20:49
* @description:
*/
public class LockSupportDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?