JUC下线程的三种等待唤醒机制
第一种:Object类中的wait和notify方法实现线程的等待和唤醒
下面标黄字的部分就是对一下两点总结的实现:
- 不能脱离synchronized代码块使用,否则会抛出IllegalMonitorStateException异常
- 先wait后notify、notifyAll,等待中的线程才能被唤醒,顺序不能改变
演示代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * @author zhangzhixi * @date 2021-5-23 18:21 */ public class LockDemo { static Object object = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized ( object ) { System. out .println(Thread.currentThread().getName() + "==>线程Come in" ); try { // 使当前线程等待 object .wait(); } catch (InterruptedException e) { e.printStackTrace(); } System. out .println(Thread.currentThread().getName() + "==>线程被唤醒" ); } }, "A" ).start(); new Thread(() -> { synchronized ( object ) { System. out .println(Thread.currentThread().getName() + "==>通知线程" ); // 使当前线程唤醒 object .notifyAll(); } }, "B" ).start(); } } |
我们来看下上面代码的执行结果,看起来很和谐:
我们来将上面代码动下手脚,注释掉10、23代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块
我们再来看一下使线程A暂停三秒会发生什么?
线程B先执行了,没有代码将线程A的wait状态进行唤醒
第二种:Condition接口中的await和single方法实现线程的等待和唤醒
- 必须配合lock()方法使用,否则抛出IllegalMonitorStateException异常
- 等待唤醒调用顺序不能改变
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class LockDemo { static Object object = new Object(); // 创建可重入锁 static Lock lock = new ReentrantLock(); static Condition condition = lock .newCondition(); public static void main(String[] args) { new Thread(() -> { lock . lock (); try { System. out .println(Thread.currentThread().getName() + "==>线程Come in" ); // 使当前线程等待 condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock .unlock(); } System. out .println(Thread.currentThread().getName() + "==>线程被唤醒" ); }, "A" ).start(); new Thread(() -> { lock . lock (); try { System. out .println(Thread.currentThread().getName() + "==>通知线程" ); // 使当前线程唤醒 condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock .unlock(); } }, "B" ).start(); } } |
看下上面代码的执行结果:
我们来将上面代码动下手脚,注释掉9 17 24 32行代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块
我们再来看一下使线程A暂停三秒会发生什么?
线程B先执行了,没有代码将线程A的await状态进行唤醒
第三种:LockSupport类中的park等待和unpark唤醒
LockSupport提供park()和unpark()方法实现阻塞和唤醒线程的过程
LockSupport和每一个使用它的线程之间有一个许可(permit)进行关联,permit有0和1两种状态,默认为0,即无许可证状态。
调用一次unpark方法,permit加1变成1。每次调用park方法都会检查许可证状态,如果为1,则消耗掉permit(1 -> 0)并立刻返回;如果为0,则进入阻塞状态。permit最多只有一个,重复调用unpark也不会累积permit。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /** * @author zhangzhixi * @date 2021-5-23 18:21 */ public class LockDemo { static Object object = new Object(); // 创建可重入锁 static Lock lock = new ReentrantLock(); static Condition condition = lock .newCondition(); public static void main(String[] args) { Thread a = new Thread(() -> { System. out .println(Thread.currentThread().getName() + "==>Come in" ); LockSupport.park(); //阻塞当前线程 System. out .println(Thread.currentThread().getName() + "==>被唤醒" ); }); a.setName( "a" ); a.start(); new Thread(() -> { LockSupport.unpark(a); System. out .println(Thread.currentThread().getName() + "==>通知" ); }, "B" ).start(); } } |
看下上面代码的执行结果:
下面给线程A进行等待3S,看会不会像一,二两个例子出现错误情况:
得出结论是:
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
总结LockSupport:
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根
结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,
调用一次unpark就加1变成1,
调用一次park会消费permit,也就是将1变成o,同时park立即返回。
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。
每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park方法时
*如果有凭证,则会直接消耗掉这个凭证然后正常退出;
*如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
面试题
为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够,不能放行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话