首页 > 其他分享 >【JDK】LockSupport 工具类

【JDK】LockSupport 工具类

时间:2024-02-09 15:12:20浏览次数:32  
标签:调用 JDK thread park LockSupport unpark 线程 工具

1  前言

LockSupport 工具类最近复习到这个类了,之前也没做笔记,这里简单回顾下哈。

JDK 中的 rt.jar 包里面的 LockSupport 是个工具类,它的主要作用是挂起和唤醒线程, 该工具类是创建锁和其他同步类的基础。 LockSupport 类与每个使用它的线程都会关联一个许可证,在默认情况下调用 LockSupport 类的方法的线程是不持有许可证的。LockSupport 是使用 Unsafe 类实现的, 下面介绍 LockSupport 中的几个主要函数。

2  重要函数

2.1  void park() 方法

如果调用 park 方法的线程已经拿到了与 LockSupport 关联的许可证,则调用 LockSupport.park() 时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻 塞挂起。

如下代码直接在 main 函数里面调用 park 方法,最终只会输出 begin park!,然后当前 线程被挂起,这是因为在默认情况下调用线程是不持有许可证的。

public static void main( String[] args ) {
    System.out.println("begin park!");
    LockSupport.park();
    System.out.println("end park!");
}

在其他线程调用 unpark(Thread thread) 方法并且将当前线程作为参数时,调用 park 方 法而被阻塞的线程会返回。另外,如果其他线程调用了阻塞线程的 interrupt() 方法,设置 了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。所以在调用 park 方法时最好也 使用循环条件判断方式。

需要注意的是,因调用 park() 方法而被阻塞的线程被其他线程中断而返回时并不会抛 出 InterruptedException 异常。

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("child thread start");
        // 阻塞  --阻塞时被中断不会抛出异常
        LockSupport.park();
        System.out.println("child thread end");
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("main begin unpark");
    // 唤醒
    // LockSupport.unpark(thread);
    // 中断
    thread.interrupt();
}

示例:

2.2  void unpark(Thread thread) 方法

当一个线程调用 unpark 时,如果参数 thread 线程没有持有 thread 与 LockSupport 类 关联的许可证,则让 thread 线程持有。如果 thread 之前因调用 park() 而被挂起,则调用 unpark 后,该线程会被唤醒。如果 thread 之前没有调用 park,则调用 unpark 方法后,再 调用 park 方法,其会立刻返回。修改代码如下。

public static void main(String[] args) {
    System.out.println( "begin park!" );
    //使当前线程获取到许可证
    LockSupport.unpark(Thread.currentThread());
    //再次调用park方法
    LockSupport.park();
    System.out.println( "end park!" );
}

该代码会输出:

begin park! 
end park!

下面再来看一个例子以加深对 park 和 unpark 的理解。

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
             System.out.println("child thread begin park!");
             // 调用park方法,挂起自己
             LockSupport.park();
             System.out.println("child thread unpark!");
        }
    });
    //启动子线程
    thread.start();
    //主线程休眠1s
    Thread.sleep(1000);
    System.out.println("main thread begin unpark!");
    //调用unpark方法让thread线程持有许可证,然后park方法返回
    LockSupport.unpark(thread);
}

输出结果为:

child thread begin park! 
main thread begin unpark! 
child thread unpark!

上面代码首先创建了一个子线程 thread, 子线程启动后调用 park 方法,由于在默认情 况下子线程没有持有许可证,因而它会把自己挂起。

主线程休眠 1s 是为了让主线程调用 unpark 方法前让子线程输出 child thread begin park! 并阻塞。

主线程然后执行 unpark 方法,参数为子线程,这样做的目的是让子线程持有许可证, 然后子线程调用的 park 方法就返回了。

park 方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用 park 方 法的原因,再次检查条件是否满足,如果不满足则还需要再次调用 park 方法。

例如,根据调用前后中断状态的对比就可以判断是不是因为被中断才返回的。

为了说明调用 park 方法后的线程被中断后会返回,我们修改上面的例子代码,删除 LockSupport.unpark(thread);,然后添加 thread.interrupt();,具体代码如下。

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("child thread begin park!");
            // 调用park方法,挂起自己,只有被中断才会退出循环
            while (!Thread.currentThread().isInterrupted()) {
                LockSupport.park();
            }
            System.out.println("child thread unpark!");
        }
    });
    // 启动子线程
    thread.start();
    // 主线程休眠1s
    Thread.sleep(1000);
    System.out.println("main thread begin unpark!");
    // 中断子线程
    thread.interrupt();
}

输出结果为:

child thread begin park! 
main thread begin unpark! 
child thread unpark!

在如上代码中,只有中断子线程,子线程才会运行结束,如果子线程不被中断,即使你调用 unpark(thread) 方法子线程也不会结束。

2.3  void parkNanos(long nanos) 方法

和 park 方法类似,如果调用 park 方法的线程已经拿到了与 LockSupport 关联的许可证, 则调用 LockSupport.parkNanos(long nanos) 方法后会马上返回。该方法的不同在于,如果 没有拿到许可证,则调用线程会被挂起 nanos 时间后修改为自动返回。

另外 park 方法还支持带有 blocker 参数的方法 void park(Object blocker) 方法,当线程 在没有持有许可证的情况下调用 park 方法而被阻塞挂起时,这个 blocker 对象会被记录到 该线程内部。

使用诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用 getBlocker(Thread) 方法来获取 blocker 对象的,所以 JDK 推荐我们使用带有 blocker 参数的 park 方法 , 并且 blocker 被设置为 this,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了。

例如下面的代码。

public class TestPark {
    public void testPark(){
        LockSupport.park();//(1)
    }
    public static void main(String[] args) {
        TestPark testPark = new TestPark();
        testPark.testPark();
    }
}

运行代码后,使用 jstack pid 命令查看线程堆栈时可以看到如下输出结果:

修改代码(1)为 LockSupport.park(this) 后运行代码,则 jstack pid 的输出结果为:

使用带 blocker 参数的 park 方法,线程堆栈可以提供更多有关阻塞对象的信息。

2.4  park(Object blocker) 方法

public static void park(Object blocker) {
    //获取调用线程
    Thread t = Thread.currentThread();
    //设置该线程的blocker变量
    setBlocker(t, blocker);
    //挂起线程
    UNSAFE.park(false, 0L);
    //线程被激活后清除blocker变量,因为一般都是在线程阻塞时才分析原因
    setBlocker(t, null);
}

Thread 类里面有个变量 volatile Object parkBlocker,用来存放 park 方法传递的 blocker 对象,也就是把 blocker 变量存放到了调用 park 方法的线程的成员变量里面。

2.5  void parkNanos(Object blocker, long nanos) 方法

相比 park(Object blocker) 方法多了个超时时间。

2.6  void parkUntil(Object blocker, long deadline) 方法

它的代码如下:

public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    //isAbsolute=true,time=deadline;表示到deadline时间后返回
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}

其中参数 deadline 的时间单位为 ms,该时间是从 1970 年到现在某一个时间点的毫秒 值。这个方法和 parkNanos(Object blocker, long nanos) 方法的区别是,后者是从当前算等待 nanos 秒时间,而前者是指定一个时间点,比如需要等到 2017.12.11 日 12:00:00,则把 这个时间点转换为从 1970 年到这个时间点的总毫秒数。

最后再看一个例子:

class FIFOMutex {
        private final AtomicBoolean locked = new AtomicBoolean(false);
        private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
        public void lock() {
            boolean wasInterrupted = false;
            Thread current = Thread.currentThread();
            waiters.add(current);
            // 只有队首的线程可以获取锁(1)
            while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
                LockSupport.park(this);
                if (Thread.interrupted()) // (2)
                    wasInterrupted = true;
            }
            waiters.remove();
            if (wasInterrupted) // (3)
                current.interrupt();
        }
        public void unlock() {
            locked.set(false);
            LockSupport.unpark(waiters.peek());
        }
    }

这是一个先进先出的锁,也就是只有队列的首元素可以获取锁。在代码(1)处,如 果当前线程不是队首或者当前锁已经被其他线程获取,则调用 park 方法挂起自己。

然后在代码(2)处判断,如果 park 方法是因为被中断而返回,则忽略中断,并且重 置中断标志,做个标记,然后再次判断当前线程是不是队首元素或者当前锁是否已经被其 他线程获取,如果是则继续调用 park 方法挂起自己。

然后在代码(3)中,判断标记,如果标记为 true 则中断该线程,这个怎么理解呢? 其实就是其他线程中断了该线程,虽然我对中断信号不感兴趣,忽略它,但是不代表其他 线程对该标志不感兴趣,所以要恢复下。

3  小结

好啦,关于 LockSupport 就看到这里哈。

标签:调用,JDK,thread,park,LockSupport,unpark,线程,工具
From: https://www.cnblogs.com/kukuxjx/p/18012474

相关文章

  • 【JDK】Random 的局限以及ThreadLocalRandom 类原理剖析
    1 前言我们平时使用随机数大家可能会用到 Random,但是它的问题大家知道吗?以及该如何解决呢?这节我们就来看看。2  Random类及其局限性在JDK7之前包括现在,java.util.Random都是使用比较广泛的随机数生成工具类,而且java.lang.Math中的随机数生成也使用的是java.util.......
  • SSH工具推荐
    结论:如果只需要同时连一台服务器,选Bitvise。需要同时连多台服务器选MobaXterm需要同时连多台服务器并且要求中文选WindTerm或者finalShellBitviseSSH优点:免费,默认支持选择复制,右键粘贴,自带文件文件上传下载,可以保存服务器账号密码到指定文件缺点:多窗口支持不好,不支持中......
  • python版本管理工具pyenv常见用法
    安装Mac使用brew进行安装:brewupdatebrewinstallpyenv配置环境变量(以zsh为例):echo'exportPYENV_ROOT="$HOME/.pyenv"'>>~/.zshrcecho'[[-d$PYENV_ROOT/bin]]&&exportPATH="$PYENV_ROOT/bin:$PATH"'>&g......
  • 数据库迁移工具--DBMotion使用教程
    DBMotion安装与使用1.访问地址DBMotion,数据库迁移|Squids.cn2.下载docker-compose.yaml点击下载docker-compose.yaml配置文件https://squids.cn/download/dbmotion/docker-compose.yamlversion:'3.0'services:dts-mysql:image:mysql:latestcontaine......
  • 用python写一个并发测试工具
    工作中会有一些需要并发测试的场景,例如:两人同时操作一条数据,此时需要验证结果是否符合预期 最初是借助jmeter来进行并发测试,建2个线程组,每个线程组中各放一个接口,启动时会同时执行个线程组中的接口,从而实现并发测试的目的但是每次都要打开jmeter,用起来不太方便,所以就尝试用pyt......
  • 【译】宣布推出适用于 .NET 的 Azure Migrate 应用程序和代码评估工具
    原文|OliaGavrysh翻译|郑子铭我们很高兴地宣布发布一款新工具,可以帮助您将本地.NET应用程序迁移到Azure。适用于.NET的AzureMigrate应用程序和代码评估工具(简称AppCAT)允许你评估应用程序的.NET源代码、配置和二进制文件,以识别将应用程序迁移到Azure时的潜在......
  • Java 中的Collections工具类
    Collections工具类java.util.Collection集合接口java.util.Collections集合工具类,方便集合操作对List集合中元素排序,需要保证List集合中元素实现了Comparable接口Collections.synchronizedList(list);设置成线程安全的Collections.sort(wuGuis);进行排序importjava......
  • jacoco覆盖率测试工具
    简介jacoco是一个能跑覆盖率的工具,可以把覆盖率结果生成报告,和IDEA自带的覆盖率测试工具类似,eclipse是没有自带覆盖率测试功能的,jacoco可以在maven执行test周期的时候生成数据,可以作为eclipse覆盖率测试工具,jacoco生成的报告可以和sonaqube,jenkin,gitlab等工具联动,实现代码门禁的......
  • 扒开源安卓性能测试工具moblieperf源码——开发属于你自己的性能稳定性测试工具
    moblieperf下载和使用moblieperf由阿里巴巴开源的Android性能测试工具下载:官方源码地址mobileperfgithub使用:使用pycharm打开下载的项目使用只需要修改配置文件config.conf即可运行采集:a.mac、linux在mobileperf工具根目录下执行shrun.sh;b.windows双击run.bat配置......
  • Jenkins在jdk17的Tomcat上运行报错
    Jenkins在jdk17的Tomcat上运行报错一、环境宝塔:tomcat8.0jdk:jdk17二、保存项目时报错​Unabletomakefieldprotectedtransientintjava.util.AbstractList.modCountaccessible:modulejava.basedoesnot"opensjava.util"tounnamedmodule@6d15ca84​查看local......