如何理解LockSupport类中的park等待和unpark唤醒
这篇文章主要讲解了"如何理解LockSupport类中的park等待和unpark唤醒",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何理解LockSupport类中的park等待和unpark唤醒"吧!
一、传统synchronized隐式锁
package com.lau.javabase.lock.LockSupport;import java.util.concurrent.TimeUnit;/** * 使用LockSupport之前,synchronized传统方式存在的问题: * 1、wait()和notify()方法不能脱离同步代码块(锁)单独使用 * 2、B线程的notify()方法在A线程的wait()之前执行的话,A线程将不会被唤醒 */public class BeforeUseTraditional { public static void main(String[] args) { Object lockObj = new Object(); //线程A new Thread(() -> {// try {// TimeUnit.SECONDS.sleep(3);// } catch (InterruptedException e) {// e.printStackTrace();// }// synchronized (lockObj){ System.out.println(Thread.currentThread().getName() + " come in..."); try { lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " awakened...");// } }, "A").start();// try {// TimeUnit.SECONDS.sleep(3);// } catch (InterruptedException e) {// e.printStackTrace();// } //线程B唤醒线程A new Thread(() -> {// synchronized (lockObj){ lockObj.notify(); System.out.println(Thread.currentThread().getName() + " notify...");// } }, "B").start(); }}
输出:
A come in...Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method) at com.lau.javabase.lock.LockSupport.BeforeUseTraditional.lambda$main$1(BeforeUseTraditional.java:42) at java.lang.Thread.run(Thread.java:745)java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.lau.javabase.lock.LockSupport.BeforeUseTraditional.lambda$main$0(BeforeUseTraditional.java:25) at java.lang.Thread.run(Thread.java:745)Process finished with exit code 0
结论:wait()和notify()方法不能脱离同步代码块(锁)单独使用
二、ReentrantLock显示锁
package com.lau.javabase.lock.LockSupport;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 使用LockSupport之前,Lock方式存在的问题: * 1、await()和signal()方法不能脱离同步代码块(锁)单独使用 * 2、B线程的和signal()方法在A线程的await()之前执行的话,A线程将不会被唤醒 */public class BeforeUseLock { public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //线程A new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } try { lock.lock(); System.out.println(Thread.currentThread().getName() + " come in..."); try { condition.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " awakened..."); } finally { lock.unlock(); } }, "A").start();// try {// TimeUnit.SECONDS.sleep(3);// } catch (InterruptedException e) {// e.printStackTrace();// } //线程B唤醒线程A new Thread(() -> { try{ lock.lock(); condition.signal(); System.out.println(Thread.currentThread().getName() + " notify..."); } finally { lock.unlock(); } }, "B").start(); }}
输出:
B notify...A come in...
结论:B线程的和signal()方法在A线程的await()之前执行的话,A线程将不会被唤醒
三、LockSupport类
package com.lau.javabase.lock.LockSupport;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.LockSupport;/** * 使用LockSupport,不会存在以下问题: * 1、await()和signal()方法不能脱离同步代码块(锁)单独使用 * 2、B线程的和signal()方法在A线程的await()之前执行的话,A线程将不会被唤醒 */public class LockSupportTest { public static void main(String[] args) { //线程A Thread threadA = new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " come in..."); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " awakened..."); }, "A"); threadA.start();// try {// TimeUnit.SECONDS.sleep(3);// } catch (InterruptedException e) {// e.printStackTrace();// } //线程B唤醒线程A new Thread(() -> { LockSupport.unpark(threadA); System.out.println(Thread.currentThread().getName() + " notify..."); }, "B").start(); }}
输出:
B notify...A come in...A awakened...
结论:使用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个,累加无效。
五、扩展
1、为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
2、为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够,不能放行。
感谢各位的阅读,以上就是"如何理解LockSupport类中的park等待和unpark唤醒"的内容了,经过本文的学习后,相信大家对如何理解LockSupport类中的park等待和unpark唤醒这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!