【Java】LockSupport原理与使用

news2024/11/17 14:35:52

LockSupport:

关键字段

    private static final sun.misc.Unsafe UNSAFE;

    private static final long parkBlockerOffset;

        Unsafe:"魔法类",较为底层,在LockSupport类中用于线程调度(线程阻塞、线程恢复等)。

        parkBlockerOffset:锁对象的内存偏移量。

关键方法

        (1)park()

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


public native void park(boolean var1, long var2);

        可以看到LockSupport的park方法实现实际是由Unsafe类完成的。在Unsafe类中,park方法被native修饰,表示方法不是由Java语言实现,而是由C、C++提供,native关键字的作用是告诉编译器方法的实现将在程序运行时通过本地调用实现。

        第一个参数为false,代表线程相对当前时间阻塞;第二个参数为0L,代表阻塞的时间长度(第一个参数为true时才起作用,表示阻塞多久)。这两个参数共同作用表示当前线程挂起,直至有线程调用unpark(Thread t)方法,或者当前阻塞线程被其它线程打断。

        (2)unpark()

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

public native void unpark(Object var1);

        方法逻辑十分简单,对传入的Thread对象进行判空操作,随后通过Unsafe类解除挂起线程的阻塞状态。

        (3)park(Object blocker)

public static void park(Object blocker) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //设置线程的锁对象
        setBlocker(t, blocker);
        //阻塞当前线程
        UNSAFE.park(false, 0L);
        //解除阻塞后,将锁对象置空
        setBlocker(t, null);
    }


//设置线程的锁对象
private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        // t:当前线程
        // parkBlockerOffset:锁对象的内存偏移量
        // arg:锁对象
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }


//返回线程的锁对象
public static Object getBlocker(Thread t) {
        //参数为null,则抛出空指针异常
        if (t == null)
            throw new NullPointerException();
        //否则使用Unsafe类通过parkBlockerOffset内存偏移量找到锁对象并返回
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }


        该方法的作用也是使当前线程阻塞,与无参的park相比多了一个锁对象:blocker,如果我们想让线程阻塞于某一对象,以便我们更好的了解和管理线程的阻塞状态就可以使用该方法。

        (4)parkUntil(long deadline)

public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

       第一个参数为true代表绝对时间阻塞;第二个参数deadline由caller线程传递,代表截止时间。两个参数共同作用表示线程会一直阻塞,直至有线程调用unpark解除阻塞、当前阻塞线程被打断、到达deadline时间,三个条件有一个成立即可脱离阻塞状态。

使用

        LockSupport的使用与Semaphore有一定相似之处,不同点在于LockSupport的permit数量变更与Semaphore不同。

park方法

        每次调用park方法都会消耗一个permit。如果线程在调用park方法时已经存在一个permit,那么当前线程不会进入阻塞状态,而是消耗掉这个permit并立即返回;如果线程在调用park方法时没有permit(初始时,默认permit为0),那么当前线程会进入阻塞状态。图解如下:

unpark方法

        unpark方法与park()方法并不一一对应:当permit数量为0个时,线程调用unpark方法会使permit数量+1;当permit数量为1个时,再次调用unpark方法不会让permit继续累加,这意味着permit的最大值为1,最小值为0。图解如下:

        测试

package test;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) {


        Thread t = new Thread(() -> {
            try {
                //这里是为了让main线程先执行unpark动作
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            long end = System.currentTimeMillis();
            System.out.println("经过:"+(end - start) +"ms后,脱离阻塞状态");
        },"Thread-1");
        t.start();

        
        System.out.println("main线程让permit数量从0变为1");
        LockSupport.unpark(t);
    }
}

        测试结果

        测试结果表明其它线程可以提前调用unpark方法,让permit数量变成1,当既定线程调用park方法时,由于permit数量不为0所以不会陷入阻塞,而是立即返回。

        让我们来看看permit能不能累加

package test;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) {


        Thread t = new Thread(() -> {
            try {
                //这里是为了让main线程先执行unpark动作
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            System.out.println("脱离阻塞状态");
        },"Thread-1");
        t.start();

        //main
        System.out.println("main线程调用两次unpark方法");
        LockSupport.unpark(t);
        LockSupport.unpark(t);
    }
}

        测试结果

        我们可以看到,即使main线程调用了两次unpark方法,但Thread-1线程还是阻塞于第二次park方法调用,这直接证明:permit不能累加,permit的最大值为1。

        LockSupport相较于传统wait/notify实现线程同步的特点是简洁,整个过程没有锁对象的参与,让我们通过三线程交替打印A、B、C来感受一下LockSupoort的魅力:

package test;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;


//三线程交替打印A、B、C,一共打印4次
public class LockSupportTest {

    static AtomicInteger i = new AtomicInteger(0);
    static Thread[] threads = new Thread[3];
    public static void main(String[] args) {

        threads[0] = new Thread(() -> {
            while (i.get() < 12){
                System.out.print("A-" + i.getAndIncrement() + "   ");
                LockSupport.unpark(threads[1]);
                LockSupport.park();
            }
        },"Thread-1");
        threads[1] = new Thread(() -> {
            LockSupport.park();
            while (i.get() < 12){
                System.out.print("B-" + i.getAndIncrement() + "   ");
                LockSupport.unpark(threads[2]);
                if (i.get() > 10)
                    break;
                LockSupport.park();
            }
        },"Thread-2");

        threads[2] = new Thread(() -> {
            LockSupport.park();
            while (i.get() < 12){
                System.out.println("C-" + i.getAndIncrement());
                LockSupport.unpark(threads[0]);
                if(i.get() > 11)
                    break;
                LockSupport.park();
            }
        },"Thread-3");

        threads[0].start();
        threads[1].start();
        threads[2].start();
    }
}

        运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1359381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【递归】C++算法:124 二叉树中的最大路径和

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 递归 124. 二叉树中的最大路径和 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#x…

【响应式编程-03】Lambda表达式底层实现原理

一、简要描述 Lambda的底层实现原理Lambda表达式编译和运行过程 二、Lambda的底层实现原理 Lambda表达式的本质 函数式接口的匿名子类的匿名对象 反编译&#xff1a;cfr-0.145.jar 反编译&#xff1a;LambdaMetafactory.metafactory() 跟踪调试&#xff0c;转储Lambda类&#x…

HarmonyOS 应用开发学习笔记 ets自定义组件及其引用 @Component自定义组件

Component注解的作用是用来构建自定义组件 Component组件官方文档 自定义组件具有以下特点&#xff1a; 可组合&#xff1a;允许开发者组合使用系统组件、及其属性和方法。 可重用&#xff1a;自定义组件可以被其他组件重用&#xff0c;并作为不同的实例在不同的父组件或容器…

防止源代码泄露的10大措施

当下&#xff0c;防止源代码泄露成为了企业和开发团队关注的焦点。 我在网上就看到过这样的案例&#xff1a; 2017年&#xff0c;一位前员工将包含公司商业机密的代码上传到了GitHub的公有仓库中&#xff0c;造成大疆源代码泄露。攻击者能够通过这些源代码访问客户敏感信息&am…

嵌入式系统复习--基于ARM的嵌入式程序设计

文章目录 上一篇编译环境ADS编译环境下的伪操作GNU编译环境下的伪操作ARM汇编语言的伪指令 汇编语言程序设计相关运算操作符汇编语言格式汇编语言程序重点C语言的一些技巧 下一篇 上一篇 嵌入式系统复习–Thumb指令集 编译环境 ADS/SDT IDE开发环境&#xff1a;它由ARM公司开…

毛虫目标检测数据集VOC格式550张

毛虫&#xff0c;一种令人惊叹的生物&#xff0c;以其独特的外貌和习性&#xff0c;成为了自然界中的一道亮丽风景。 毛虫的外观非常特别&#xff0c;身体呈圆柱形&#xff0c;表面覆盖着许多细小的毛发&#xff0c;这使得它们在叶子上伪装得非常好。它们的头部有一对坚硬的颚…

vmware安装centos 7.6 操作系统

vmware安装centos 7.6 操作系统 1、下载centos 7.6 操作系统镜像文件2、安装centos 7.6操作系统3、配置centos 7.6 操作系统3.1、配置静态IP地址 和 dns3.2、查看磁盘分区3.3、查看系统版本 1、下载centos 7.6 操作系统镜像文件 这里选择 2018年10月发布的 7.6 版本 官方下载链…

flutter学习-day23-使用extended_image处理图片的加载和操作

文章目录 1. 介绍2. 属性介绍3. 使用 1. 介绍 在 Flutter 的开发过程中&#xff0c;经常会遇到图片的显示和加载处理&#xff0c;通常显示一个图片&#xff0c;都有很多细节需要处理&#xff0c;比如图片的加载、缓存、错误处理、图片的压缩、图片的格式转换等&#xff0c;如果…

代码随想录 718. 最长重复子数组

题目 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1…

创新性文生视频模型,南洋理工开源FreeInit

文本领域的ChatGPT&#xff0c;画图领域的Midjourney都展现出了大模型强大的一面&#xff0c;虽然视频领域有Gen-2这样的领导者&#xff0c;但现有的视频扩散模型在生成的效果中仍然存在时间一致性不足和不自然的动态效果。 南洋理工大学S实验室的研究人员发现&#xff0c;扩散…

前端 -- 基础 路径 -- 相对路径 详解

目录 导语引入 &#xff1a; 相对路径 &#xff1a; 相对路径 包含哪些 &#xff1a; 同一级路径 &#xff1a; 下一级路径 &#xff1a; 上一级路径 &#xff1a; 导语引入 &#xff1a; # 大家都清楚&#xff0c;在我们日常所见到的网页里&#xff0c;要涉及好多…

imgaug库指南(五):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

微信小程序如何搜索iBeacon设备

1.首先在utils文件夹下创建bluetooth.js和ibeacon.js 2.在 bluetooth.js文件中写入 module.exports {initBluetooth: function () {// 初始化蓝牙模块wx.openBluetoothAdapter({success: function (res) {console.log(蓝牙模块初始化成功);},fail: function (res) {console.l…

C++ 释放指针

在C中&#xff0c;释放指针通常使用delete或delete[]操作符&#xff1b; 如果指针指向的是单个对象&#xff0c;可以使用delete操作符进行释放&#xff1b; 在释放完内存后&#xff0c;最好将指针置为nullptr&#xff0c;以避免出现悬空指针&#xff08;dangling pointer&#…

【大数据】Zookeeper 集群及其选举机制

Zookeeper 集群及其选举机制 1.安装 Zookeeper 集群2.如何选取 Leader 1.安装 Zookeeper 集群 我们之前说了&#xff0c;Zookeeper 集群是由一个领导者&#xff08;Leader&#xff09;和多个追随者&#xff08;Follower&#xff09;组成&#xff0c;但这个领导者是怎么选出来的…

wpsjs学习——获取单元格批注

1.获取第一个单元格的值&#xff1a; wps.Application.ActiveSheet.Range(A1).Value2; 2.1.获取第一个单元格的批注&#xff1a; wps.Application.ActiveSheet.Range(A1).Comment.Text(); <div class"global">获取表格信息<div class"divItem">…

vr眼镜和AR眼镜的区别有哪些?哪些产品可以支持VR应用?

vr眼镜怎么连接手机 要将VR眼镜连接到手机上&#xff0c;您可以按照以下步骤进行&#xff1a; 1. 确保您的手机支持VR应用程序&#xff1a;首先&#xff0c;确保您的手机具备运行VR应用程序的硬件和软件条件。一些VR应用程序可能对设备有特定的要求&#xff0c;如处理器性能、操…

x-cmd pkg | pdfcpu - 强大的 PDF 处理工具

目录 简介首次用户多功能支持性能表现安全的加密处理进一步阅读 简介 pdfcpu 是一个用 Go 编写的 PDF 处理库。同时它也提供 API 和 CLI。pdfcpu 提供了丰富的 PDF 操作功能&#xff0c;用户还能自己编写配置文件&#xff0c;用来管理和使用各种自定义字体并存储有效的默认配置…

VR与数字孪生:共同构筑未来的虚拟世界

随着科技的不断发展&#xff0c;数字孪生和VR已经成为当今热门的科技话题。作为山海鲸可视化软件的开发者&#xff0c;我们对这两者都有深入的了解。在此&#xff0c;我们将详细探讨数字孪生与VR的区别和联系。 首先&#xff0c;数字孪生&#xff08;Digital Twin&#xff09;…

洛谷——P1983 [NOIP2013 普及组] 车站分级(拓扑排序、c++)

文章目录 一、题目[NOIP2013 普及组] 车站分级题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 二、题解基本思路&#xff1a;代码 一、题目 [NOIP2013 普及组] 车站分级 题目背景 NOIP2013 普及组 T4 题目描述 一条单…