park与unpark基本使用与原理分析

简介: park与unpark基本使用与原理分析

一、概念

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。

park,unpark这两个方法都是LockSupport类名下的方法,park用来暂停线程,unpark用来将暂停的线程恢复。

先park再unpark的方式是容易理解的。但还有一个场景,先unpark后再次执行park方法,也不会阻塞调用了park方法的线程。

理解为park方法就是校验获取一个通行令牌,而unpark方法是获取到一个通行令牌的过程。先执行unpark方法,代表先获得了通行令牌。那么在另一个线程调用park方法时,校验到这个令牌存在,消耗掉这个令牌然后就可以继续往下走。

二、LockSupport函数列表

public class LockSupport {
    // 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
    static Object getBlocker(Thread t);
    // 为了线程调度,禁用当前线程,除非许可可用。
    static void park();
    // 为了线程调度,在许可可用之前禁用当前线程。
    static void park(Object blocker);
    // 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
    static void parkNanos(long nanos);
    // 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
    static void parkNanos(Object blocker, long nanos);
    // 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
    static void parkUntil(long deadline);
    // 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
    static void parkUntil(Object blocker, long deadline);
    // 如果给定线程的许可尚不可用,则使其可用。
    static void unpark(Thread thread);
}

说明:LockSupport是通过调用Unsafe函数中的接口实现阻塞和解除阻塞的。

park()与park(Object blocker)区别说明

其他对应方法同样的原理,故以park()与park(Object blocker)为例进行说明

这两个方法都是用于线程调度的,但是有所不同:

  1. park() 方法会将当前线程挂起(暂停),直到被唤醒或者中断。
  2. park(Object blocker) 方法与 park() 方法类似,但是它还可以指定一个“阻塞器”对象,以便更好地进行调试和分析。当线程被阻塞时,阻塞器对象会被记录下来,方便程序员进行问题定位。

下面是使用代码举例:

// 示例 1: park()
Thread thread1 = new Thread(() -> {
    System.out.println("线程开始运行");
    LockSupport.park(); // 线程被挂起
    System.out.println("线程被唤醒");
});
thread1.start();
Thread.sleep(3000);
LockSupport.unpark(thread1); // 唤醒线程
// 示例 2: park(Object blocker)
Object blocker = new Object();
Thread thread2 = new Thread(() -> {
    System.out.println("线程开始运行");
    LockSupport.park(blocker); // 线程被挂起,并记录阻塞器对象
    System.out.println("线程被唤醒");
});
thread2.start();
Thread.sleep(3000);
LockSupport.unpark(thread2); // 唤醒线程
// 打印出阻塞器对象
System.out.println(LockSupport.getBlocker(thread2)); // 输出 blocker

在示例 2 中,我们使用了一个阻塞器对象 blocker,当线程被阻塞时,该对象会被记录下来。在最后一行代码中,我们打印出了线程 thread2 的阻塞器对象,输出结果为 "blocker"。

三、基本使用

  • 先 park 再 unpark
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
import static site.weiyikai.concurrent.utils.Sleeper.sleep;
/**
 * Created by xiaowei
 * Date 2022/10/22
 * Description 先 park 再 unpark
 */
@Slf4j(topic = "c.Test01")
public class Test01 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            sleep(1);
            log.debug("park...");
            LockSupport.park();
            log.debug("resume...");
        },"t1");
        t1.start();
        sleep(2);
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

输出结果

21:16:35.017 c.Test01 [t1] - start...
21:16:36.019 c.Test01 [t1] - park...
21:16:37.017 c.Test01 [main] - unpark...
21:16:37.017 c.Test01 [t1] - resume...
  • 先 unpark 再 park
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
import static site.weiyikai.concurrent.utils.Sleeper.sleep;
/**
 * Created by xiaowei
 * Date 2022/10/22
 * Description 先 unpark 再 park
 */
@Slf4j(topic = "c.Test02")
public class Test02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            sleep(2);
            log.debug("park...");
            LockSupport.park();
            log.debug("resume...");
        },"t1");
        t1.start();
        sleep(1);
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

输出结果

21:18:03.732 c.Test02 [t1] - start...
21:18:04.731 c.Test02 [main] - unpark...
21:18:05.735 c.Test02 [t1] - park...
21:18:05.735 c.Test02 [t1] - resume...

与Object的wait¬ify对比

  • wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必;
  • park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】;
  • park & unpark 可以先 unpark,而 wait & notify 不能先 notify。

四、原理

每个线程都会关联一个 Parker 对象,每个 Parker 对象都各自维护了三个角色:_counter(计数器)、 _mutex(互斥量)、_cond(条件变量)。

4.1 情况一:先调用park,再调用unpark

park 操作

  1. 当前线程调用 Unsafe.park() 方法
  2. 检查 counter ,本情况为 0,这时,获得 mutex 互斥锁
  3. 线程进入 _cond 条件变量阻塞
  4. 设置 _counter = 0
unpark 操作

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 唤醒 _cond 条件变量中的 Thread_0
  3. Thread_0 恢复运行
  4. 设置 _counter 为 0

4.2 情况二:先调用unpark,再调用park

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 当前线程调用 Unsafe.park() 方法
  3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  4. 设置 _counter 为 0
目录
相关文章
|
存储 关系型数据库 MySQL
【分布式和微服务1】一篇文章详细了解分布式和微服务的基本概念
【分布式和微服务1】一篇文章详细了解分布式和微服务的基本概念
2244 0
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1184 22
|
7月前
|
负载均衡 Dubbo Cloud Native
分布式 RPC 深度拆解:Dubbo 与 gRPC 底层原理、核心差异与生产级调优实战
本文深入剖析RPC核心本质与通用架构,详解Dubbo 3.x(Java生态企业级框架)和gRPC(云原生跨语言框架)的底层原理、性能差异、生产调优及避坑指南,涵盖动态代理、序列化、网络传输、服务发现、集群容错等关键模块,助力构建高可用分布式系统。
1030 3
|
Java API 调度
说一说 LockSupport 的 park 和 unpark
我是小假 期待与你的下一次相遇 ~
393 1
|
Arthas 监控 Java
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
|
存储 架构师 安全
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
NoSQL 中间件 Java
字节面试:聊聊 CAP 定理?哪些中间件是AP? 哪些是CP? 说说 为什么?
45岁老架构师尼恩在其读者交流群中分享了关于CAP定理的重要面试题及其解析,包括CAP定理的基本概念、CAP三要素之间的关系,以及如何在分布式系统设计中权衡一致性和可用性。文章还详细分析了几种常见中间件(如Redis Cluster、Zookeeper、MongoDB、Cassandra、Eureka、Nacos)的CAP特性,并提供了高端面试技巧,帮助读者在面试中脱颖而出。尼恩还推荐了其团队编写的《尼恩Java面试宝典PDF》等资料,助力求职者准备面试,提升技术水平。