1.
Lock Support从字面意思直接翻译过来是锁的支撑类,是一个改进类。下面就看一下这个技术为什么会出现,解决了那些老技术不方便不灵活的问题。
官方解释:用于创建锁和其他同步类的基本线程阻塞原语。
核心就是park()
和unpark()
方法
- park()方法是阻塞线程
- unpark()方法是解除阻塞线程
LockSupport就是对线程等待和唤醒机制的另外一种机制和提升
2.线程等待唤醒机制
3种让线程等待和唤醒的方法:
1、使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
2、使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
3、LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
2.1.方式一实现
Object类中的wait和notify方法实现线程等待和唤醒
正常代码:
public class LockSupportDemo { public static void main(String[] args) { Object objectLock = new Object(); // 同一把锁,类似资源类 new Thread(() -> { synchronized (objectLock) { System.out.println(Thread.currentThread().getName()+"\t ---- come in"); try { objectLock.wait();//----------------------这里先让他等待 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了"); },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { synchronized (objectLock) { objectLock.notify();//-------------------------再唤醒它 System.out.println(Thread.currentThread().getName()+"\t ---发出通知"); } },"t2").start(); } }
public class LockSupportDemo { public static void main(String[] args) { Object objectLock = new Object(); new Thread(() -> { // synchronized (objectLock) { System.out.println(Thread.currentThread().getName()+"\t ---- come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // } System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了"); },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { // synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName()+"\t ---发出通知"); // } },"t2").start(); } }
执行结果:出现了监视器不对的异常
t1 ---- come in Exception in thread "t1" java.lang.IllegalMonitorStateException: current thread is not owner at java.base/java.lang.Object.wait(Native Method) at java.base/java.lang.Object.wait(Object.java:338) at com.fblinux.JUC.LockSupportDemo.lambda$main$0(LockSupportDemo.java:14) at java.base/java.lang.Thread.run(Thread.java:833) Exception in thread "t2" java.lang.IllegalMonitorStateException: current thread is not owner at java.base/java.lang.Object.notify(Native Method) at com.fblinux.JUC.LockSupportDemo.lambda$main$1(LockSupportDemo.java:26) at java.base/java.lang.Thread.run(Thread.java:833)
public class LockSupportDemo { public static void main(String[] args) { Object objectLock = new Object(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName()+"\t ---- come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了"); },"t1").start(); new Thread(() -> { synchronized (objectLock) { objectLock.notify();//这个先执行了 System.out.println(Thread.currentThread().getName()+"\t ---发出通知"); } },"t2").start(); } }
执行结果: 程序一直卡住,并没有正常的退出和后续的执行
t2 ---发出通知 t1 ---- come in
2.2.方式二实现
Condition接口中的await后signal方法实现线程的等待和唤醒
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()+"\t-----come in"); condition.await(); System.out.println(Thread.currentThread().getName()+"\t -----被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName()+"\t"+"我要进行唤醒"); },"t2").start(); } }
执行结果:
t1 -----come in t2 我要进行唤醒 t1 -----被唤醒
- 异常1原理同上:仍然返回`IllegalMonitorStateException`
- 异常2 原理同上:仍然在不停的循环
结论:`await`和`notify`类似于上面`wait`和`notify`
- Condition中的线程等待和唤醒方法,需要先获取锁
- 一定要先await后signal,不能反了
3.
3.1.是什么
Lock Support是用来创建锁和其他同步类的基本线程阻塞原语。
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
- LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
- permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒
- 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
3.2.主要方法
阻塞:park()/park(Object blocker) 阻塞当前线程/阻塞传入的具体线程
permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程发放permi通行证t,park方法才会被唤醒。
唤醒:unpack(Thread thread)唤醒处于阻塞状态的指定线程
调用unpark(thread)方法后,就会将thread线程的许可证permit发放,会自动唤醒park线程,即之前阻塞中的Lock Support.park()方法会立即返回。
3.3.代码
public class LockSupportDemo { public static void main(String[] args) { Thread t1 = new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t----------come in"); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了"); },"t1"); t1.start(); new Thread(()->{ LockSupport.unpark(t1); // 参数中传递发放通行证的线程名称 System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1"); },"t2").start(); } }
执行结果:
t1 ----------come in t2 -----发出通知,去唤醒t1 t1 ----------被唤醒了
之前错误的先唤醒后等待,LockSupport照样支持
public class LockSupportDemo { 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()+"\t----------come in"+"\t"+System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了"+"\t"+System.currentTimeMillis()); },"t1"); t1.start(); new Thread(()->{ LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1"); },"t2").start(); } }
执行结果:
t2 -----发出通知,去唤醒t1 t1 ----------come in 1675220291150 t1 ----------被唤醒了 1675220291151
sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下
4.LockSupport总结
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport 是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,Lock Support调用的Unsafe中的native代码。
LockSupport提供park()和unpack()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会累计凭证。
形象的理解
线程阻塞需要消耗凭证(permit) , 这个凭证最多只有1个。
当调用park方法时
如果有凭证,则会直接消耗掉这个凭证然后正常退出;
如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。
为什么可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后在调用park方法,就可以名正言顺的凭证消费,故不会阻塞。先发放了凭证后续可以畅通无阻。
转载请注明:西门飞冰的博客 » LockSupport和线程等待唤醒机制