首页 > 其他分享 >JUC工具(LockSupport)

JUC工具(LockSupport)

时间:2024-04-26 11:56:02浏览次数:22  
标签:JUC 许可 Thread park LockSupport unpark 线程 工具

LockSupport用来创建锁和其他同步类的基本线程阻塞

LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行

LockSupport介绍

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和解除阻塞的。LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:

  • park:阻塞当前线程(Block current thread),字面理解park,就算占住,停车的时候不就把这个车位给占住了么?起这个名字还是很形象的。
  • unpark: 使给定的线程停止阻塞(Unblock the given thread blocked )。

因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

private static void setBlocker(Thread t, Object arg)
// 返回提供给最近一次尚未解除阻塞的 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)

两个重点

(1)操作对象

归根结底,LockSupport调用的Unsafe中的native代码:

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    public static void park() {
        UNSAFE.park(false, 0L);
    }

两个函数声明清楚地说明了操作对象:park函数是将当前Thread阻塞,而unpark函数则是将指定线程Thread唤醒

与Object类的wait/notify机制相比,park/unpark有两个优点:

  1. 以thread为操作对象更符合阻塞线程的直观定义;
  2. 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

(2)关于许可

在上面的文字中,我使用了阻塞和唤醒,是为了和wait/notify做对比。

  • 其实park/unpark的设计原理核心是“许可”。park是等待一个许可。unpark是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
  • 有一点比较难理解的,是unpark操作可以再park操作之前。也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。
  • 但是这个“许可”是不能叠加的,“许可”是一次性的。比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

park和unpark的灵活之处

上面已经提到,unpark函数可以先于park调用,这个正是它们的灵活之处。

一个线程它有可能在别的线程unPark之前,或者之后,或者同时调用了park,那么因为park的特性,它可以不用担心自己的park的时序问题,否则,如果park必须要在unpark之前,那么给编程带来很大的麻烦!!

考虑一下,两个线程同步,要如何处理?

在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。 编程的时候就会很蛋疼。

另外,是调用notify,还是notifyAll?

notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了。而unpark可以指定到特定线程。

park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态。

代码示例

LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。

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

运行该代码,可以发现主线程一直处于阻塞状态。因为 许可默认是被占用的 ,调用park()时获取不到许可,所以进入阻塞状态。

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。

arduino复制代码public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//释放许可
     LockSupport.park();// 获取许可
     System.out.println("b");
}

LockSupport是可不重入 的,如果一个线程连续2次调用 LockSupport.park(),那么该线程一定会一直阻塞下去。

public static void main(String[] args) throws Exception
{
    Thread thread = Thread.currentThread();
    
    LockSupport.unpark(thread);
    
    System.out.println("a");
    LockSupport.park();
    System.out.println("b");
    LockSupport.park();
    System.out.println("c");
}

这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。

下面我们来看下LockSupport对应中断的响应性

public static void t2() throws Exception
{
    Thread t = new Thread(new Runnable()
    {
        private int count = 0;

        @Override
        public void run()
        {
            long start = System.currentTimeMillis();
            long end = 0;

            while ((end - start) <= 1000)
            {
                count++;
                end = System.currentTimeMillis();
            }

            System.out.println("after 1 second.count=" + count);

            //等待或许许可
            LockSupport.park();
            System.out.println("thread over." + Thread.currentThread().isInterrupted());

        }
    });

    t.start();

    Thread.sleep(2000);

    // 中断线程
    t.interrupt();

    System.out.println("main over");
}

最终线程会打印出thread over.true。这说明 线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException

总结

Thread.sleep()和Object.wait()的区别

首先,我们先来看看Thread.sleep()和Object.wait()的区别,这是一个烂大街的题目了,大家应该都能说上来两点。

  • Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁;
  • Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;
  • Thread.sleep()到时间了会自动唤醒,然后继续执行;
  • Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;
  • Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;

其实,他们俩最大的区别就是Thread.sleep()不会释放锁资源,Object.wait()会释放锁资源。

Object.wait()和Condition.await()的区别

Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。

实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用LockSupport.park()阻塞当前线程。

Thread.sleep()和LockSupport.park()的区别

LockSupport.park()还有几个兄弟方法——parkNanos()、parkUtil()等,我们这里说的park()方法统称这一类方法。

  • 从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;
  • Thread.sleep()没法从外部唤醒,只能自己醒过来;
  • LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;
  • Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;
  • LockSupport.park()方法不需要捕获中断异常;
  • Thread.sleep()本身就是一个native方法;
  • LockSupport.park()底层是调用的Unsafe的native方法;

Object.wait()和LockSupport.park()的区别

二者都会阻塞当前线程的运行,他们有什么区别呢? 经过上面的分析相信你一定很清楚了,真的吗? 往下看!

  • Object.wait()方法需要在synchronized块中执行;
  • LockSupport.park()可以在任意地方执行;
  • Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;
  • LockSupport.park()不需要捕获中断异常;
  • Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
  • LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;

park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的Semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证。

如果在wait()之前执行了notify()会怎样?

如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出IllegalMonitorStateException异常;

如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。

如果在park()之前执行了unpark()会怎样?

线程不会被阻塞,直接跳过park(),继续执行后续内容

LockSupport.park()会释放锁资源吗?

不会,它只负责阻塞当前线程,释放锁资源实际上是在Condition的await()方法中实现的

标签:JUC,许可,Thread,park,LockSupport,unpark,线程,工具
From: https://www.cnblogs.com/luojw/p/18159737

相关文章

  • ETL工具-nifi干货系列 第十七讲 nifi Input Port&Out Port 实战教程
    1、端口(Port),包含输入端口(InputPort)和输出端口(OutPort )使用一个或多个处理组构建的数据流需要一种方式将处理组连接到其他数据流组件。处理组和处理组之间可以通过使用端口来进行连接。这里的端口和kettle中的步骤【复制记录到结果】、【从结果获取记录】是类似的功能。数据......
  • PEcmd是一个命令行工具,用于执行各种操作系统和文件系统相关的任务。通常,它用于在Windo
    PECMD命令帮助-PECMD技术社区:www.pecmd.netPEcmd是一个命令行工具,用于执行各种操作系统和文件系统相关的任务。通常,它用于在Windows操作系统上执行各种文件和目录操作,例如文件复制、移动、删除等。PEcmd提供了一种简单而强大的方式来管理文件和目录,尤其是在自动化和批处......
  • mountvol命令是Windows操作系统中的一个命令行工具,用于管理卷挂载点和卷名称。通过mou
    mountvol|MicrosoftLearnmountvol命令是Windows操作系统中的一个命令行工具,用于管理卷挂载点和卷名称。通过mountvol命令,用户可以查看当前系统中的卷挂载点信息、创建新的卷挂载点、删除已存在的卷挂载点等操作。具体来说,mountvol命令的主要功能包括:列出卷挂载点:通过......
  • Pycharm图形化性能测试工具Profile
     在部署模型的时候遇到一个需要加速的问题,为了搞清楚模型调用过程中最耗时的操作以定位优化,同事告诉了我一个调优的工具,叫profile。搜集整理如下: 1.PyCharm图形化性能测试工具ProfilePyCharm提供了图像化的性能分析工具,使用方法见利用PyCharm的Profile工具进行Python性......
  • JUC工具(Exchange)
    Exchanger(交换器),顾名思义,用于两个线程之间进行数据交换两个线程通过exchange()方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据API方法构造方法Exchanger():创建一个交换器......
  • 【笔记】拓扑图工具调研
    一、在线拓扑图编辑工具零代码三维地图开发http://www.emapgis.com/数字孪生https://www.hightopo.com/demos/index.htmlvue-antvx6-demo推荐。https://gitee.com/yanggengzhen/vue-antvx6-demo/tree/masterhttps://qunee.com/数百个HTML5例子学习HT图形组件–WebGL3D......
  • 日期工具类,获取全年每月有几周,分别是多少,且每周对应的是几号到几号
    一、概述项目需要做日程组件,其中涉及到了日历这块的内容。需求:1.获取全年有多少个月2.获取每月有多少个周3.获取每月每一天对应的是星期几4.单独获取某一天对应的是星期几5.把以上四条组织成为一个集合二、代码示例/**......
  • shell脚本文本处理工具
    声明:以下内容为个人笔记,内容不完全正确,请谨慎参考。文本处理工具cut:cut工作是“剪”,具体来说就是在文件中负责剪切数据。cut命令从文件的每个行剪切字节、字符和字段输出。1、基本语法:cut[选项参数]filename说明:默认分隔符是副表符2、选项参数说明选项参数:1)-f2)-......
  • WPF 使用 ManipulationDemo 工具辅助调试设备触摸失效问题
    本文将和大家介绍我所在的团队开源的ManipulationDemo工具。通过ManipulationDemo工具可以提升调试设备触摸失效的效率此工具在GitHub上完全开源,请看https://github.com/dotnet-campus/ManipulationDemo/软件界面效果大概如下可以显示接收到的Win32消息、当前的触摸......
  • 推荐一个使用 HardLink 硬链接减少重复文件占用磁盘空间的工具
    在NTFS文件系统里面,咱可以使用HardLink硬链接的方式,将多个重复的文件链接到磁盘的同一份记录里面,从而减少在磁盘里面对重复文件存储多份记录,减少磁盘空间的占用。本文将和大家推荐我所做的基于HardLink硬链接减少重复文件占用磁盘空间的工具此工具名为UsingHardLinkToZipN......