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
目录
相关文章
|
6月前
|
SQL 分布式计算 Hadoop
【Spark】Spark基础教程知识点
【Spark】Spark基础教程知识点
|
6月前
|
分布式计算 Hadoop Java
Spark_Day01:Spark 框架概述和Spark 快速入门
Spark_Day01:Spark 框架概述和Spark 快速入门
95 0
|
分布式计算 资源调度 大数据
Spark 源码初步剖析_1 | 学习笔记
快速学习 Spark 源码初步剖析_1
106 0
|
分布式计算 Spark 索引
Spark2.4.0源码分析之WorldCount ShuffleMapTask处理(八)
- 理解Executor中是如何调用Task的过程 - 理解ShuffleMapTask是处理过程
1620 0
|
分布式计算 Spark 机器学习/深度学习
Spark2.4.0源码分析之WorldCount FinalRDD构建(一)
- Spark dataSet执行计算转成FinalRDD - FinalRdd从第一个RDD到最到一个RDD的转化过程 - RDD之间的依赖引用关系 - ShuffleRowRDD默认分区器为HashPartitioning,实际new Partitioner,分区个数为200
1113 0
|
分布式计算 Spark
Spark2.4.0 SparkEnv 源码分析
SparkEnv对象构建 SparkEnv类中做如下操作 ).new SecurityManager() ).new NettyRpcEnvFactory() ).创建NettyRpcEnv ).
1759 0
|
分布式计算 Spark Hadoop
Spark MapOutputTracker源码分析
## 技能标签 - Spark ShuffleMapTask处理完成后,把MapStatus数据(BlockManagerId,[compressSize])发送给MapOutputTrackerMaster.
1694 0
|
分布式计算 Java API
spark2.1.0之配置与源码分析
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/80468207       任何优秀的软件或服务都会提供一些配置参数,这些配置参数有些是内置的,有些则是可以由用户配置的。
1322 0
|
分布式计算 Spark
Spark2.1命令工具类CommandUtils的源码分析
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/77450103 注:本文是为了配合《Spark内核设计的艺术 架构设计与实现》一书的内容而编写,目的是为了节省成本、方便读者查阅。
1365 0