写点什么

话说 LockSupport

发布于: 2021 年 04 月 05 日

LockSupport

LockSupport 要从 wait/notify/notifyAll 、condition.await/signal/signalAll 说起


在 JUC 包中很多地方都会使用到 LockSupport 比如我们前边写的 ReentrantLock 中,获取锁失败之后会加入队列调用 LockSupport.park() 等待前边一个获取锁的线程 unpark 自己


下边以小强和小月月用同一个水杯喝水为例 讲解 wait/notify 、await/signal 、park/unpark


有问题可以公众号留言:木子的昼夜编程

一、 wait notify

wait nofity 的详细讲解 前边文章有写过 这里只是简单实用 仅为了与 LockSupport 比较


class TestWaitNotify {    public Object obj = new Object();    public static void main(String[] args) {        TestWaitNotify test = new TestWaitNotify();        new Thread(test::xiaoqiang).start();        new Thread(test::xiaoyueyue).start();    }
/** * 小强喝水 */ public void xiaoqiang(){ synchronized (obj){ while (true){ try{ System.out.println("小强喝水"); //通知别人喝水 obj.notify(); // 自己wait obj.wait(); } catch (Exception e){ e.printStackTrace(); } } } }
/** * 小月月喝水 */ public void xiaoyueyue(){ synchronized (obj){ while (true){ try{ System.out.println("小月月喝水"); //通知别人喝水 obj.notify(); // 自己wait obj.wait(); } catch (Exception e){ e.printStackTrace(); } } } }
}
输出结果:小强喝水小月月喝水小强喝水小月月喝水小强喝水小月月喝水...
复制代码


小强喝完了通知别人可以喝了,然后自己等着


小月月收到通知,喝水,喝完了通知别人可以喝了,然后自己等着


如此循环往复...


wait/notify/notifyAll 还是老生常谈的问题:


1. 必须在synchronized代码块或者synchronized修饰的方法内  否则会报异常:java.lang.IllegalMonitorStateException2. nofity只有在wait方法调用之后调用才能生效  先调用notify再调用wait 没用。。。3. 线程interrupt 中断会打断wait 
复制代码
二、await signal
class TestAwaitSignal {    Lock lock = new ReentrantLock();    Condition cd = lock.newCondition();    public static void main(String[] args) {        TestAwaitSignal test = new TestAwaitSignal();
new Thread(test::xiaoqiang).start(); new Thread(test::xiaoyueyue).start(); }
/** * 小强喝水 */ public void xiaoqiang(){ try { lock.lock(); while (true){ try{ System.out.println("小强喝水"); //signal通知别人喝水 cd.signal(); // 自己await cd.await(); } catch (Exception e){ e.printStackTrace(); } } } catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } }
/** * 小月月喝水 */ public void xiaoyueyue(){ try { lock.lock(); while (true){ try{ System.out.println("小月月喝水"); //signal通知别人喝水 cd.signal(); // 自己await cd.await(); } catch (Exception e){ e.printStackTrace(); } } } catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } }}
输出结果:小强喝水小月月喝水小强喝水小月月喝水小强喝水小月月喝水小强喝水小月月喝水小强喝水...
复制代码


await/signal/signalAll 还是老生常谈的问题:


1. 必须在lock获取锁代码块中执行 否则会报IllegalMonitorStateException异常2. lock必须在finally中手动释放锁3. signal 必须 在await之后调用  否则不起作用 4. condition.await比wait多可中断、到达指定时(前边文章有写)间停止等功能(前边文章有写)5. 一个lock可以new多个互不干扰的condition (前边文章有写)
复制代码

三、LockSupport

3.1 使用

这是要给很好的东西


park /pɑːk/ 停车场、停放


unpark 把车从停车场离开


从一个故事走进 park/unpark ,小强开着车走进一个停车场 park 了一下,然后他就出不去了,只能一直在停车场停着,直到小月月给他送来停车券 unpark,这时候他才能出去


停车例子:


class TestParkUnpark {
public static void main(String[] args) throws InterruptedException { TestParkUnpark test = new TestParkUnpark(); Thread thread = new Thread(test::xiaoqiang); thread.start();
Thread.sleep(5000); // 小月月给停车券 new Thread(()->{ test.xiaoyueyue(thread); }).start(); }
/** * 小强进入停车场 */ public void xiaoqiang(){ System.out.println("停车等着出停车场"); LockSupport.park(); System.out.println("我出来了!!"); }
/** * 小月月给停车券 */ public void xiaoyueyue(Thread thread){ System.out.println("给小强那二傻子送个停车券"); LockSupport.unpark(thread); }}输出:停车等着出停车场给小强那二傻子送个停车券我出来了!!
复制代码


很明显看出来了,


1.LockSupport 与wait/condition.await最大的区别就是 他不要锁,不需要在锁代码块中使用
复制代码
3.2 不分顺序

LockSupport 还有一个特点就是 unpark 可以先于 park 执行,就是小强这二傻子去停车场之前,小月月先把停车券给他了 unpark,等他想走的时候手里有停车券不需要再一次获取停车券 unpark 了。


class TestParkUnpark02 {
public static void main(String[] args) throws InterruptedException { TestParkUnpark02 test = new TestParkUnpark02(); Thread thread = new Thread(test::xiaoqiang);
// 进入停车场 thread.start();
// 小月月先给停车券 new Thread(()->{ test.xiaoyueyue(thread); }).start();
}
/** * 小强进入停车场 */ public void xiaoqiang() { try { // 在外边潇洒呢 Thread.sleep(10000); // 进停车场了 System.out.println("停车等着出停车场"); LockSupport.park(); System.out.println("我出来了!!"); } catch (Exception e){
} }
/** * 小月月给停车券 */ public void xiaoyueyue(Thread thread){ System.out.println("给小强那二傻子送个停车券"); LockSupport.unpark(thread); }}输出结果:给小强那二傻子送个停车券停车等着出停车场我出来了!!
复制代码
3.3 可中断 可超时返回 可指定等待时间点返回
class TestParkUnpark03 {    public static void main(String[] args)   {        TestParkUnpark03 test = new TestParkUnpark03();        // 可中断 停止park        Thread thread = new Thread(test::xiaoqiang);        thread.start();        thread.interrupt();

} /** * 可中断 */ public void xiaoqiang() { // 进停车场了 System.out.println("停车等着出停车场"); LockSupport.park(); boolean interrupted = Thread.currentThread().isInterrupted(); if (interrupted){ System.out.println("被中断的 强行闯卡"); } else { System.out.println("拿到票 我出来了!!"); } }}
复制代码


class TestParkUnpark04 {    public static void main(String[] args)   {        TestParkUnpark04 test = new TestParkUnpark04();        // 可超时        Thread thread = new Thread(test::xiaoqiang);        thread.start();

} /** * 可超时 */ public void xiaoqiang() { // 进停车场了 System.out.println("停车等着出停车场"); // 5秒后自己出去 long nanos = TimeUnit.SECONDS.toNanos(5); LockSupport.parkNanos(nanos); System.out.println("结束park 要么是给了券 要么是小强跟门卫耗了半天让他出去了"); }}
复制代码


class TestParkUnpark04 {    public static void main(String[] args)   {        TestParkUnpark04 test = new TestParkUnpark04();        // 可指定等待时间点 到这个点就停止park         Thread thread = new Thread(test::xiaoqiang);        thread.start();

} /** * 可指定等待时间点 到这个点就停止park */ public void xiaoqiang() { // 进停车场了 System.out.println("停车等着出停车场"); // 10秒后自己出去 long nanos = TimeUnit.SECONDS.toMillis(10); Date date= new Date(System.currentTimeMillis()+nanos); // 到指定时间就停止 LockSupport.parkUntil(date.getTime()); System.out.println("结束park 要么是给了券 要么是小强跟门卫耗了半天让他出去了 要么是到了晚上(指定时间)门卫不在 自己溜出去了"); }}
复制代码
3.4 还有一个 功能

这个功能乍一看没啥作用。


class TestParkUnpark04 {    public static void main(String[] args)   {        TestParkUnpark04 test = new TestParkUnpark04();        new Thread(test::xiaoqiang01,"线程001").start();        new Thread(test::xiaoqiang02,"线程002").start();
}
public void xiaoqiang01() { // 这里传入了一个对象 LockSupport.park(); } public void xiaoqiang02() { // 这里传入了一个对象 LockSupport.park(new ParkParam()); }}class ParkParam{
}
复制代码


我们用 jps 找到当前 java 进程,再用 jstack 查看堆栈信息


1. 打开cmd2. jps  11672 TestParkUnpark043. jstack -l 11672部分输出内容: "线程002" #12 prio=5 os_prio=0 tid=0x000000001e042800 nid=0x263c waiting on condition [0x000000001eaff000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        - parking to wait for  <0x000000076bbb1a20> (a vip.freeedu.ParkParam)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)        at vip.freeedu.TestParkUnpark04.xiaoqiang02(TestParkUnpark04.java:25)        at vip.freeedu.TestParkUnpark04$$Lambda$2/990368553.run(Unknown Source)        at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers: - None
"线程001" #11 prio=5 os_prio=0 tid=0x000000001e03f800 nid=0x25fc waiting on condition [0x000000001e9ff000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304) at vip.freeedu.TestParkUnpark04.xiaoqiang01(TestParkUnpark04.java:21) at vip.freeedu.TestParkUnpark04$$Lambda$1/2003749087.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)
复制代码



可以看到线程 002 多了一行 -parking for wait for xxxx


这样我们在排查问题的时候就容易知道是哪儿出了问题 ,每个 park 处都 new 不一样的对象,这样很容易定位

3.5 LockSupport**优点出来了
1. 不用与锁混合使用!! 2. 可以先unpark拿到停车券 在park的时候就可以直接用停车券通过!!3. 可以超时停止等待 ; wait await都支持4. 可停止到指定时间 ;await支持5. 可中断 ;wait await都支持  await支持不响应中断
复制代码


这里说一点哈 ,Object 的 wait 也支持 wait 超时停止等待

3.6 注意:unpark 不能累计

park 和 unpark 的实现原理。。 我看了一下 hotspot 的实现 没有看懂 ,但是我看注释意思可能就是这样的,


有一个标志位**_event** ,

3.7 park

当调用 park 的时候就会判断**_event**值


  1. 如果是-1 非法数据

  2. 如果是 1 那就设置为 0 返回(不阻塞直接过)相当于手持停车券 出停车场 顺畅无阻 (先 unpark 再 park )

  3. 如果是 0 那就把**_event**设置为-1


void os::PlatformEvent::park() {       // AKA "down()"  // Transitions for _event:  //   -1 => -1 : illegal  //    1 =>  0 : pass - return immediately  //    0 => -1 : block; then set _event to 0 before returning
复制代码



3.8 unpark

当调用 unpark****的时候就会判断_event**值


  1. 如果是 0,设置**_event**为 1 这就是为什么 unpark 可以在 park 之前调用,因为这里设置为 1 然后 park 的时候就会用这个值

  2. 如果是 1 返回 ,不会累积 这就是为什么 unpark 多次只能 park 使用一次

  3. 如果是-1 那就把**_event**设置为 0 或 1 ,唤醒目标线程


void os::PlatformEvent::unpark() {  // Transitions for _event:  //    0 => 1 : just return   //    1 => 1 : just return  //   -1 => either 0 or 1; must signal target thread  //         That is, we can safely transition _event from -1 to either  //         0 or 1.
复制代码



用户头像

还未添加个人签名 2018.03.28 加入

还未添加个人简介

评论

发布
暂无评论
话说  LockSupport