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

作者:暮色妖娆丶 时间:2023-11-29 17:16:10 

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++ 了。。。。

来源:https://juejin.cn/post/7192019058399641658

标签:LockSupport,线程阻塞,唤醒
0
投稿

猜你喜欢

  • VS2010+Opencv+MFC读取图像和视频显示在Picture控件

    2023-09-28 03:39:07
  • winform实现限制及解除鼠标移动范围的方法

    2023-07-17 22:11:51
  • RecyclerView仿应用列表实现网格布局

    2021-10-24 19:35:31
  • Android简单实现圆盘抽奖界面

    2022-07-25 08:35:27
  • Java使用DOM4j实现读写XML文件的属性和元素

    2021-10-19 00:21:52
  • c# 模拟串口通信 SerialPort的实现示例

    2023-09-03 22:19:50
  • java多线程复制文件的实例代码

    2022-03-04 04:29:54
  • Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

    2023-08-10 07:43:21
  • Android下拉刷新控件PullToRefresh实例解析

    2022-01-27 08:01:14
  • 【Java IO流】字节流和字符流的实例讲解

    2023-08-08 20:45:58
  • java poi解析word的方法

    2023-08-28 07:31:28
  • Java线程休眠的5种方法

    2022-02-21 04:49:48
  • Spring注解配置实现过程详解

    2022-04-06 11:12:52
  • java内存模型jvm虚拟机简要分析

    2022-09-08 09:29:34
  • Linux+Docker+SpringBoot+IDEA一键自动化部署的详细步骤

    2023-08-12 15:25:39
  • 基于SpringBoot实现自定义插件的流程详解

    2021-08-15 20:34:04
  • C#实现俄罗斯方块

    2023-10-29 05:44:27
  • C# Color.FromArgb()及系统颜色对照表一览

    2021-05-24 08:21:02
  • Springcloud seata分布式事务实现代码解析

    2022-12-27 20:14:01
  • Dwr3.0纯注解(纯Java Code配置)配置与应用浅析二之前端调用后端

    2023-08-19 17:32:33
  • asp之家 软件编程 m.aspxhome.com