并发编程学习(八):ReentrantLock

news2024/9/29 23:23:03

ReentrantLock 是java.util.concurrent.locks包下的类。

相对于synchronized,它具备如下特性:

  1. 可中断。

  1. 可以设置超时时间。

  1. 可以设置公平锁。

  1. 支持多个条件变量。即可以有个多个waitset等待队列。

  1. 与synchronized都支持可重入。

ReentrantLock的基本语法:

// 获取锁
reentrantLock.lock();
try {
    // 临界区代码
} finally {
    // 释放锁
    reentrantLock.unlock();
}
    • 可重入

可冲入是指 同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可冲入锁,那么第二次获取锁时,自己会被锁挡住

测试代码示例:

/**
 * ReentrantLock 可重入 代码测试。
 */
@Slf4j(topic = "test.ReentrantLockTest1")
public class ReentrantLockTest1 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        reentrantLock.lock();
        try {
            log.debug("enter main...");
            method1();
        } finally {
            reentrantLock.unlock();
        }
    }
    public static void method1() {
        reentrantLock.lock();
        try {
            log.debug("enter method1...");
            method2();
        } finally {
            reentrantLock.unlock();
        }
    }
    public static void method2() {
        reentrantLock.lock();
        try {
            log.debug("enter method2...");
        } finally {
            reentrantLock.unlock();
        }
    }
}

执行结果:

2、可中断

reentrantLock.lockInterruptibly();

如果没有竞争,此方法会让当前线程t1 获取到reentrantLock对象锁。

如果有竞争,会进入阻塞队列,但可以被其它线程用 t1.interrupt()方法打断阻塞,进而让线程t1继续执行。

可以防止线程无限制的等待下去(因为 t1.interrupt() 可以打断t1线程).

测试代码示例如下:

/**
 * ReentrantLock 可中断特性 代码测试。
 */
@Slf4j(topic = "test.ReentrantLockTest2")
public class ReentrantLockTest2 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
       Thread t1 = new Thread(() -> {
           try {
               log.debug("尝试获取reentrantLock锁");
               reentrantLock.lockInterruptibly();
           } catch (InterruptedException e) {
               e.printStackTrace();
               log.error("没有获取reentrantLock锁,返回");
               return;
           }

           try {
               log.debug("获取到reentrantLock锁");
           } finally {
               reentrantLock.unlock();
           }
       },"t1");

        // main线程获得reentrantLock锁
        reentrantLock.lock();
        // t1线程启动后,
        // 如果reentrantLock存在竞争,t1线程无法获取锁;
        // 如果没有竞争,reentrantLock.lockInterruptibly()会让t1线程获取到reentrantLock对象锁。
        t1.start();
        Thread.sleep(1000);
        log.debug("打断t1线程,让t1线程进入catch(InterruptedException)");
        t1.interrupt();
    }
}

执行结果:

3、设置超时时间

boolean reentrantLock.tryLock(); 可以用来尝试获得锁。

boolean reentrantLock.tryLock(long timeout,TimeUnit unit); 可以用来尝试获得锁,并设置超时时间。调用此方法期间,可以调用 t1.interrupt()方法进行打断。

测试代码示例:

/**
 * ReentrantLock  设置超时时间 代码测试。
 */
@Slf4j(topic = "test.ReentrantLockTest3")
public class ReentrantLockTest3 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
       Thread t1 = new Thread(() -> {
           try {
               log.debug("尝试获取锁");
               if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)) {
                   log.debug("获取不到锁");
                   return;
               }
           } catch (InterruptedException e) {
               e.printStackTrace();
               log.error("获取不到锁,被打断。");
               return;
           }
           try {
               log.debug("获取到锁");
           } finally {
               reentrantLock.unlock();
           }
       },"t1");

        // main线程获得reentrantLock锁
        try {
            reentrantLock.lock();
            log.debug("main线程获取到锁");
            t1.start();
            Thread.sleep(1000);
        } finally {
            log.debug("main线程释放锁");
            reentrantLock.unlock();
        }
        t1.interrupt();
    }
}

使用tryLock()解决哲学家就餐死锁问题,代码示例如下:

/**
 *  reentrantLock.lock(); 解决哲学家就餐问题。
 */
@Slf4j(topic = "test.ReentrantLockTest4")
public class ReentrantLockTest4 {
    public static void main(String[] args) {
        // 五根筷子
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");

        // 五个哲学家吃饭
        new Philosopher("哲学家1",c1,c2).start();
        new Philosopher("哲学家2",c2,c3).start();
        new Philosopher("哲学家3",c3,c4).start();
        new Philosopher("哲学家4",c4,c5).start();
        new Philosopher("哲学家5",c5,c1).start();
    }

    // 哲学家类
    @Slf4j(topic = "test.Philosopher")
    static class Philosopher extends Thread {
        private Chopstick left;
        private Chopstick right;

        public Philosopher(String name, Chopstick left, Chopstick right) {
            super(name);
            this.left = left;
            this.right = right;
        }

        @Override
        public void run() {
            while (true) {

                // synchronized 会造成死锁
//                // 尝试获取左手筷子
//                synchronized (left) {
//                    // 尝试获取右手筷子
//                    synchronized (right) {
//                        eat();
//                    }
//                }

                // 尝试获取左手筷子
                if (left.tryLock()) {
                    try {
                        // 尝试获取右手筷子
                        if (right.tryLock()) {
                            try {
                                eat();
                            } finally {
                                right.unlock();
                            }
                        }
                    } finally {
                        left.unlock();
                    }
                }
            }
        }

        private void eat() {
            log.debug("eating...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 筷子类
    @Slf4j(topic = "test.Chopstick")
    static class Chopstick extends ReentrantLock {
        private String name;

        public Chopstick (String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "筷子{" + name + "}";
        }
    }
}

4、设置公平锁

synchronized是不公平锁,锁释放时,阻塞队列中的多个线程去竞争。

ReentrantLock默认是不公平的,但可以设置为公平锁(即锁释放时,根据进入阻塞队列的顺序,先来先得)。一般不会设置公平锁,会降低并发度。

源码如下:可以通过构造方法设置公平性。

5、条件变量

synchronized中也有条件变量,就是monitor中的waitset休息室,当条件不满足时进入waitset等待。

ReentrantLock的条件变量比synchronized的强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized是哪些不满足条件的线程都在一间休息室等消息。

  • 而ReentrantLock支持多间休息室,有专门等烟的休息室、专门等早餐的休息室,唤醒时也是按休息室来唤醒。

使用流程:

  • await前需要获得锁。

  • await执行后,会释放锁,进入conditionObject等待。

  • await的线程被唤醒(或打断、或超时),重新竞争锁。

  • 竞争锁成功后,从await后继续执行。

代码示例如下:

/**
 *  ReentrantLock 之 await、newCondition 的使用:条件等待
 */
@Slf4j(topic = "test.ReentrantLockTest5")
public class ReentrantLockTest5 {
    private static boolean hasCigarette = false; // 是否有烟的标识
    private static boolean hasTakeout = false;   // 是否外卖的标识
    private static ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock锁
    private static Condition waitCogaretteSet = lock.newCondition();// 等烟的休息室
    private static Condition waitTakeoutSet = lock.newCondition();  // 等外卖的休息室

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            lock.lock(); // 获取锁
            try {
                log.debug("有烟吗?[{}]",hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        // await前需要获得锁;
                        // await执行后,会释放锁,进入waitCogaretteSet等待。
                        waitCogaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
                lock.unlock(); // 释放锁
            }
        },"小南").start();

        new Thread(() -> {
            lock.lock(); // 获取锁
            try {
                log.debug("外卖送到没?[{}]",hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        // await前需要获得锁;
                        // await执行后,会释放锁,进入waitTakeoutSet等待。
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
                lock.unlock(); // 释放锁
            }
        },"小刘").start();

        Thread.sleep(1000);
        new Thread(() -> {
            try {
                lock.lock();
                hasCigarette = true;      // 有烟了
                waitCogaretteSet.signal();// 等烟休息室中唤醒一个等烟线程
            } finally {
                lock.unlock();
            }
        },"送烟的").start();

        Thread.sleep(1000);
        new Thread(() -> {
            try {
                lock.lock();
                hasTakeout = true;      // 有外卖了
                waitTakeoutSet.signal();// 等外卖休息室中唤醒一个等外卖线程
            } finally {
                lock.unlock();
            }
        },"送外卖的").start();
    }
}

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

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

相关文章

数学建模相关竞赛零基础上手与入门介绍

文章目录1、赛事介绍与报名2、学习与训练2.1 比赛题目选择范围2.2 赛前组队与分工2.3 比赛时间分配1、赛事介绍与报名 什么是数学建模? 定义: 生活中的各种问题(如股票预测、火灾报警统计等),运用数学的方式去阐述并解决它。 数学建模赛事 …

cisp证书含金量怎么样?值不值得考?

这是CISP考试报名条件参考: 成为CISP,必须满足以下基本要求: 申请CISE、CISO注册资质,需满足以下教育和工作经验要求: (1)教育和工作经历要求:硕士及硕士以上学历,具备…

LabVIEW在实时目标上使用文件路径

LabVIEW在实时目标上使用文件路径文件路径和结构因目标操作系统而异。本文档讨论了推荐的LabVIEW编码实践,用于指定文件路径,以便应用程序可以无缝地从目标移动目标。实时操作系统选项所有NI实时控制器运行三种不同的操作系统之一,即PharLap、…

Find My资讯|美国航班取消,出行者疯狂购买苹果AirTag追踪行李箱

美国西南航空(Southwest Airlines Co.)由于所使用的 SkySolver 系统在圣诞假期间崩溃,导致航班出现大面积延误或取消(大约 13000 个航班受到影响),让公司损失超过 8 亿美元(当前约 53.76 亿元人…

5.kafka--生产调优

文章目录Leader Partition负载均衡消费者初始化流程消费者再平衡生产者和消费者如何提高吞吐量如何发送大消息Leader Partition负载均衡 参数名称描述auto.leader.rebalance.enable默认是true。自动LeaderPartition平衡。生产环境中,leader重选举的代价比较大&…

ADB快速入门

ADB快速入门 一、 简介 Android Debug Bridge,我们一般简称为adb,主要存放在sdk安装目录下的platform-tools文件夹中,它是一个非常强大的命令行工具,通过这个工具用来连接电脑和Android设备(手机、电脑、电视、平板、…

LIO-SAM代码解析——imuPreintegration.cpp

目录imuPreintegration.cpp1. TransformFusion 类1.1. lidarOdometryHandler1.2. imuOdometryHandler2. IMUPreintegration 类2.1. imuHandler2.2. odometryHandler⭐2.2.1. 初始化系统, 把初始的lidar位姿,速度,零偏加入到因子图中2.2.2. 将两帧之间的i…

【深度学习】简述CNN分类网络的演变脉络及各自的贡献与特点

问题 简述CNN分类网络的演变脉络及各自的贡献与特点 综述 深度学习的浪潮就是从CNN开始的,它结构形态的变化也见证着这门技术的发展。现在涌进来学习深度学习的大部分人都是做计算机视觉的,因为这个门槛相对较低,业界数据集开源了很多,也比较直观,而且对硬件设备的要求…

2023年二月份图形化四级打卡试题

活动时间 从2023年 1月1日至1月21日,每天一道编程题。 本次打卡的规则如下: (1)小朋友每天利用10~15分钟做一道编程题,遇到问题就来群内讨论,我来给大家答疑。 (2)小朋友做完题目后&…

Spring Boot 整合Redis分布式锁 Lua脚本

参考:微服务 Spring Boot 整合Redis分布式锁 Lua脚本 实现优惠卷秒杀 一人一单_Bug 终结者的博客-CSDN博客 一、什么是Lua? Lua 是一个小巧的脚本语言。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编…

MySQL事务学习笔记

事务就是保证一组数据库操作,要么全部成功,要么全部失败。事务的实现是在引擎层, 因此我们说的是InnoDB的事务。为何需要事务?比如有一个转钱的业务,A给B转100, 那么就是两条sql语句,一个是A的钱…

Spring Boot 热部署(热加载)

idea 热部署作用&#xff1a;自动帮开发者重启 spring boot 项目&#xff0c;从而达到修改代码之后能够“实时”的看到最新的效果1.添加热部署框架支持<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</…

Spring和Spring Boot的区别

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Spring和Spring Boot的区别 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: …

双目立体匹配(传统/深度)方法总结

双目立体匹配工作--2022年度总述投影几何标定单目标定双目标定校正立体匹配传统方法深度学习方法合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式总述 投影几何 标定 摄像机参数&#xff1a;一般一共15个相关参数&#xff1a; &#xff08;1&#xff09;外参数…

详解目前最火的自主泊车技术

/ 导读 /谈起目前的自动驾驶技术&#xff0c;民众们最为熟悉的一定是已经走入寻常百姓家的辅助驾驶功能。对比起L4甚至更高级别的自动驾驶&#xff0c;司机们更相信将方向盘能时刻掌握在自己手中&#xff0c;如果有突发情况可以及时地进行接管。而目前消费者已经能体验到的辅助…

prometheus安装及使用入门

文章目录前言一. prometheus介绍1.1 prometheus的起源2.1 prometheus的特点二. prometheus的安装2.1 实验环境2.2 安装前准备2.3 开始安装prometheus2.3.1 下载并解压prometheus server2.3.2 安装过程2.3.3 启动并查看端口2.3.4 打开浏览器查看图形界面总结前言 云原生四象限&…

【应用】SpringCloud -- Sentinel

SpringCloud -- SentinelSentinel 概述Sentinel 的安装与启动Sentinel 微服务创建Sentinel 规则流控规则热点规则熔断规则授权规则Sentinel 其他常用功能规则持久化自定义异常Sentinel 概述 sentinel 官方文档 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越…

三十五、Kubernetes1.25中Ingress使用

1、概述在上篇文章中我们详解销售了Ingress是什么、有什么用以及怎么安装&#xff0c;如果没有看的建议先看下&#xff0c;然后再来看这篇文章&#xff0c;上篇文章地址&#xff1a;https://blog.csdn.net/u011837804/article/details/128564606这篇文章我们用实际操作&#xf…

vs鼠标右键“管理NuGet程序包”无响应

其他C#开发问题参考我的&#xff1a;C#基础知识体系框架图&#xff0c;及起对应我发过的博客 问题&#xff1a;下载SharpDX的开源示例代码&#xff0c;鼠标右键无法正常打开NuGet下载依赖&#xff0c;而在本地自己创建的项目中可以正常下载 原因&#xff1a;缺少.sln文件 解决…

定时任务Demo总结(推荐最后一种)

方法一&#xff1a; 线程实现 Runnable 接口 Thread thread new Thread(new Runnable() {Overridepublic void run() {while (true) {SimpleDateFormat sdf new SimpleDateFormat("HH:mm:ss.SSS");String dateStr sdf.format(new Date());System.out.println(&quo…