线程阻塞唤醒工具 LockSupport使用详解

 更新时间:2023年01月26日 13:10:35   作者:暮色妖娆丶  
这篇文章主要为大家介绍了线程阻塞唤醒工具LockSupport使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

LockSupport 简介

LockSupport 是 Java 并发编程中一个非常重要的组件,我们熟知的并发组件 Lock、线程池、CountDownLatch 等都是基于 AQS 实现的,而 AQS 内部控制线程阻塞和唤醒又是通过 LockSupport 来实现的。

从该类的注释上也可以发现,它是一个控制线程阻塞和唤醒的工具,与以往的不同是它解决了曾经 wait()、notify()、await()、signal() 的局限。

回顾 synchronized 和 Lock

我们知道 Java 中实现并发安全通常会通过这两种加锁的方式,对于 synchronized 加锁的方式,如果我们想要控制线程的阻塞和唤醒是通过锁对象的 wait()notify() 方法,以下面循环交替打印 AB 为例

int status = 2;
public static void main(String[] args) throws InterruptedException {
    TestSync obj = new TestSync();
     new Thread(() -> {
        synchronized (obj){
            while (true){
                if(obj.status == 1){
                    obj.wait();
                }
                System.out.println("A");
                obj.status = 1;
                TimeUnit.SECONDS.sleep(1);
                obj.notify();
            }
        }
     }).start();
    new Thread(() -> {
       synchronized (obj){
          while (true){
              if(obj.status == 2){
                  obj.wait();
              }
              System.out.println("B");
              obj.status = 2;
              TimeUnit.SECONDS.sleep(1);
              obj.notify();
          }
       }
    }).start();
}

如果我们使用 Lock 实现类,上述代码几乎是一样的,只是先获取 Condition 对象

 Condition condition = lock.newCondition();

obj.wait() 换成 condition.await()obj.notify() 换成 condition.signal() 即可。

LockSupport 和 synchronized 和 Lock 的阻塞方式对比

技术阻塞唤醒方式局限
synchronized使用锁对象的 wait()、notify()1. 只能用在 synchronized 包裹的同步代码块中 2. 必须先 wait() 才能 notify()
Lock使用 condition 的 await()、signal()1. 只能用在 lock 锁住的代码块中 2. 必须先 await() 才能 signal()
LockSupportpark()、unpark(Thread t)没有限制

LockSupport 的使用

下面代码中,我们使用 LockSupport 去阻塞和唤醒线程,我们可以多次尝试,LockSupportpark()unpark() 方法没有先后顺序的限制,也不需要捕获异常,也没有限制要在什么代码块中才能使用。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("A");
            LockSupport.park();
            System.out.println("被唤醒");
        });
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        new Thread(() -> {
            System.out.println("B");
            LockSupport.unpark(t1);
        }).start();
    }

LockSupport 注意事项

许可证提前发放

从该类的注释中我们可以看到这个类存储了使用它的线程的一个许可证,当调用 park() 方法的时候会判断当前线程的许可证是否存在,如果存在将直接放行,否则就阻塞。

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("A");
        LockSupport.park();//不会阻塞
        System.out.println("被唤醒");
    });
    t1.start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        System.out.println("B");
        System.out.println("先调用 unpark()");
        LockSupport.unpark(t1);
    },"t2").start();
}

看这个代码示例,这里我们在 t2 中先让线程 t1 unpark(), 然后在 t1 中调用 park(), 结果并不会阻塞 t1 线程。因为在 t2 中调用 LockSupport.unpark(t1); 的时候相当于给 t1 提前准备好了许可证。

许可证不会累计

LockSupport.unpark(t1); 无论调用多少次,t1 的通行证只有一个,当在 t1 中调用两次 park() 方法时线程依然会被阻塞。

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("A");
        LockSupport.park();
        LockSupport.park();
        System.out.println("被唤醒");
    });
    t1.start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        System.out.println("B");
        System.out.println("先调用 unpark()");
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
    },"t2").start();
}

以上述代码为例,t1 将被阻塞。

LockSupport 底层实现

观察源码发现 park() 和 unpark() 最底下调用的是 native() 方法,源码在 C++ 中实现

@IntrinsicCandidate
public native void park(boolean isAbsolute, long time);
@IntrinsicCandidate
public native void unpark(Object thread);

对,这只是个标题,卷不动了,不去看 C/C++ 了。。。。

结语

LockSupport 是 Java 并发编程中非常重要的组件,这是我们下一步阅读 AQS(AbstractQueuedSynchronizer) 源码的基础。总之我们只要记住它是控制线程阻塞和唤醒的工具,并且知道它与其他阻塞唤醒方式的区别即可。

以上就是线程阻塞唤醒工具 LockSupport使用详解的详细内容,更多关于唤醒 LockSupport线程阻塞的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot+Thymeleaf+ECharts实现大数据可视化(基础篇)

    SpringBoot+Thymeleaf+ECharts实现大数据可视化(基础篇)

    本文主要介绍了SpringBoot+Thymeleaf+ECharts实现大数据可视化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧<BR>
    2022-06-06
  • Intellij IDEA下Spring Boot热切换配置

    Intellij IDEA下Spring Boot热切换配置

    这篇文章主要介绍了Intellij IDEA下Spring Boot热切换配置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Servlet虚拟路径映射配置详解

    Servlet虚拟路径映射配置详解

    这篇文章主要介绍了Servlet虚拟路径映射配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • java实现滑动验证解锁

    java实现滑动验证解锁

    这篇文章主要为大家详细介绍了java实现滑动验证解锁,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • 教你在SpringBoot中管理多环境配置文件

    教你在SpringBoot中管理多环境配置文件

    在项目开发中,一般会分为开发环境,测试环境和生产环境.如果总是在一个文件中修修改改,难免会出现错误,尤其是在交接的过程中,一旦出现问题,轻则丢失绩效,重则需要删库跑路.本文则带大家详细了解怎么管理多环境配置文件,需要的朋友可以参考下
    2021-05-05
  • @ComponentScan在spring中无效的原因分析及解决方案

    @ComponentScan在spring中无效的原因分析及解决方案

    这篇文章主要介绍了@ComponentScan在spring中无效的原因分析及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • SpringBoot yml配置文件调用过程解析

    SpringBoot yml配置文件调用过程解析

    这篇文章主要介绍了SpringBoot yml配置文件调用过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Mybatis的sql语句执行异常后打印到日志问题

    Mybatis的sql语句执行异常后打印到日志问题

    文章介绍了一种Mybatis异常日志打印方案,主要通过Mybatis拦截器获取执行的sql语句,并利用ThreadLocal存储,以避免多线程下的sql语句覆盖问题,当异常发生时,从ThreadLocal中取出sql语句并打印到单独的日志文件中,方便数据恢复,该方案经过压力测试
    2024-10-10
  • springboot调用webservice-soap接口的实现

    springboot调用webservice-soap接口的实现

    接口协议目前广泛使用的有http协议和RPC协议和webservice,本文主要介绍了springboot调用webservice-soap接口的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • java实现学生宿舍系统

    java实现学生宿舍系统

    这篇文章主要为大家详细介绍了java实现学生宿舍系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03

最新评论