并发编程学习(八):ReentrantLock特性、哲学家吃饭问题

news2024/9/24 21:23:16

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

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

  1. 可中断。

  1. 可以设置超时时间。

  1. 可以设置公平锁。

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

  1. 与synchronized都支持可重入。

ReentrantLock的基本语法:

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

1、可重入

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

测试代码示例:

/**
 * 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/187646.html

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

相关文章

c++11 标准模板(STL)(std::multiset)(二)

定义于头文件 <set>template< class Key, class Compare std::less<Key>, class Allocator std::allocator<Key> > class multiset;(1)namespace pmr { template <class Key, class Compare std::less<Key>> using…

Day12【元宇宙的实践构想01】—— 元宇宙概念和发展历程

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 ✍每日一句&#xff1a;【道固远&#xff0c;笃行可至&#xff1b;事虽巨&#xff0c;坚为必成】 &#x1f6a9; 今日留言&#xff1a;亮亮被迫去练科目二啦&#xff0c;定时…

K8S架构熟悉及日常操作

目录 一、架构介绍 二、组件介绍 三、调度介绍 四、CLI指令介绍 五、常见CLI指令 六、常见问题排查思路 一、架构介绍 Kubernetes系统架构为客户端/服务端&#xff08;C/S&#xff09;架构&#xff0c;Master作为服务端&#xff0c;Node作为客户端。 Master服务端也被称…

学习逆向安全的必备基础: 汇编的初步了解

什么是汇编 汇编语言是一种低级编程语言&#xff0c;它使用简单的助记符来表示计算机底层的机器指令。 汇编语言是直接与计算机硬件交互的&#xff0c;它能够控制计算机中的每一个细节。 由于汇编语言非常低级&#xff0c;所以编写汇编程序通常比较困难。不过&#xff0c;汇…

微信怎样开发小程序【公司企业小程序开发】

现在很多公司企业都有自己的小程序&#xff0c;没有小程序的公司企业也会寻找开发小程序的途径。那么今天就给大家简单介绍微信怎样开发小程序&#xff0c;希望对需要开发小程序的公司企业有帮助。 一、注册小程序账号 有一个小程序账号是必须的&#xff0c;小程序账号可以在…

那些外贸老鸟们都在认真使用的8个实用小工具

在我们日常的外贸工作中&#xff0c;有很多地方都可以用到一些实用外贸小工具去提高工作效率&#xff0c;突破局部限制。是否能够灵活的应用这些实用外贸小工具&#xff0c;是一位成熟优秀外贸业务员的衡量标准之一。第一个&#xff1a;知识信息整理和CRMhttps://www.notion.so…

Linux(六)基础I/O

引言 C语言进阶 文件管理 上一篇文章详细回顾了C语言方面关于文件操作的一些库函数&#xff0c;比如输入输出重定向fscanf、fprintf&#xff0c;对于文件内容以字符形式读取的fgetc、fputc&#xff0c;对于文件内容以字符串形式读取的fgets、fputs&#xff0c;对于二进制文件的…

ieee会议论文从手稿到发表

0. 前言 在创新点得到认可之后就可以准备发论文了&#xff0c;这个一定要早点&#xff0c;可以给自己设置一个明确的deadline&#xff0c;毕竟ddl是第一生产力。 1. 确定发什么期刊、会议 一定要符合学校的毕业要求&#xff0c;有一些水的学校并不认。时间看能不能赶上学校毕…

物联网智慧消防对比传统消防具有哪些优势?

随着科技的进步和城市化进程的加快&#xff0c;传统消防已经满足不了社会发展的需求&#xff0c;智慧消防应运而生&#xff0c;目前智慧消防已经成为消防安全管理的核心&#xff0c;物联网时代的到来&#xff0c;让智慧消防迎来了更大的发展机遇&#xff0c;变得更加智慧化、系…

手机网站建设怎么做?【手机网站制作】

对于很多公司企业来说&#xff0c;做网站建设都是优先考虑PC端的网站建设&#xff0c;但是某些公司企业可能对于PC端网站的需求不高&#xff0c;倒是更有需要做移动端网站&#xff0c;也就是我们常说的手机网站。那么关于手机网站建设又是怎么做的呢&#xff1f;本文给大家做一…

软件测试员年底总结怎么写?所有问题都帮你梳理好了!

临近年底&#xff0c;很多公司都有年终总结环节&#xff0c;核心目的发现今年的不足&#xff0c;进而总结经验&#xff0c;更好地用以指导明年的工作。当然&#xff0c;即使公司没有要求&#xff0c;对于测试岗位来说&#xff0c;一年一度的总结不可或缺。假如你是测试负责人&a…

<使用Python自定义生成简易二维码>——《Python项目实战》

目录 1.问题导引 2.实现步骤 &#xff08;1&#xff09;查找并安装第三方库qrcode &#xff08;2&#xff09;编写代码并嵌入内置信息 &#xff08;3&#xff09;使用扫码工具读取信息 后记&#xff1a;●由于作者水平有限&#xff0c;文章难免存在谬误之处&#xff0c;敬…

【Tkinter】终于把StringVar讲明白了

文章目录简介Label使用StringVarEntry输入简介 初学者在使用tkinter时常犯的一个错误就是 def changeText(evt):evt.text "new Text"毕竟在创建控件时&#xff0c;text是出现频率很高的参数&#xff0c;换言之&#xff0c;我们会默认控件中有text这个属性&#xf…

CDGA/CDGP数据治理认证班将于2/4正式开课,报名从速!

新的一年&#xff0c;从考证开始&#xff0c;为职场竞争增添更多优势&#xff01; 做数据行业的话&#xff0c;当然推荐考个DAMA-CDGA/CDGP数据管理证书啦&#xff01; DAMA是全球唯一数据管理方面权威性认证&#xff0c;帮助数据从业者提升数据管理能力。 DAMA认证为数据管理专…

【c语言进阶】文件操作(下)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我…

[NOIP2008 提高组] 笨小猴

题目描述 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设 maxn 是单词中出现次数最多的字母的…

Python 中当前时间表示方法详解

在 Python 中获取当前时间是许多与时间有关的操作的一个很好的起点。一个非常重要的用例是创建时间戳。在本教程中&#xff0c;你将学习如何用 datetime 模块获取、显示和格式化当前时间。我们将学习如何读取当前时间的属性&#xff0c;如年份、分钟或秒。为了使时间更容易阅读…

函数的求导法则——“高等数学”

今天&#xff0c;小雅兰的内容是函数的求导法则&#xff0c;上篇博客我们知道了导数的定义、导数的几何意义及可导与连续关系&#xff0c;这篇博客我们来仔细学习一下求导法则&#xff0c;下面&#xff0c;就让我们进入导数的世界吧 一、函数的和、差、积、商的求导法则 二、反…

Beryl Li 代表 YGG 出席 2023 年世界经济论坛会议

Yield Guild Games&#xff08;YGG&#xff09;联合创始人 Beryl Li 代表 YGG 参加了 2023 年 1 月 16 日至 20 日在瑞士达沃斯举行的 2023 年世界经济论坛年会 &#xff08;WEF23&#xff09;&#xff0c;在全球舞台上分享区块链、通证化、数字资产监管、治理和价值创造的潜力…

C++语法复习笔记-4. C++基本容器

文章目录1.数组声明与定义数组的开闭区间差一错误左闭右开非对称区间原则数组的增删改查一维数组二维数组面向对象的动态数组-vector自动扩容增删改查2. 字符串字符串变量与常量unicode编码字符串指针表示方法指针的表示方法char[]和char* 的区别数组每个值可改指针指向的字符串…