ReentrantLock

news2024/12/24 10:15:05

目录

ReentrantLock

ReentrantLock语法

ReentrantLock可重入

ReentrantLock可打断

ReentrantLock锁超时

ReentrantLock解决哲学家就餐问题

ReentrantLock公平锁

ReentrantLock条件变量


ReentrantLock

ReentrantLock 相比于synchronized的特点 :

  • 可中断:比如A线程拥有锁,B线程能给他取消掉
  • 可以设置超时时间,规定一段时间竞争锁,如果获取不到锁,那么就不竞争了做一些其他的逻辑.
  • 可以设置为公平锁 ,防止线程饥饿的情况,大家都在排队等,先到的先得到锁.
  • 支持多个条件变量 ,相当于waitSet(条件不满足等待的地方),支持多个WaitSet里面等

相当于一个房子有不同的休息室,对其进行细分.

相同点 : 都是支持可重入的.同一个线程可以对同一个对象反复加锁.

ReentrantLock语法

//创建一个ReentrantLock对象
private static ReentrantLock lock =new ReentrantLock();
public static void main(String[] args) {
    //调用lock方法加锁
    lock.lock();
    try{
        //临界区的代码
    }finally {
        //必须对其进行解锁,所以放在finally块
        lock.unlock();
    }
}

ReentrantLock可重入

Synchronized和ReentrantLock都是可重入锁.

可重入锁也就是同一个线程首次获得了这把锁,也就是称为这把锁的拥有者,因此有权利再次获取这把锁,也就是同一个线程可以对同一个对象反复加锁

相反之,就有不可重入锁.

不可重入锁就是当第二次获取锁的时候,就会被锁挡住.

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.ReentrantFeatures")
public class ReentrantFeatures {
    /**
     * ReentrantLock可重入特性演示
     * @param args
     */
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("enter main");
           m1();
        }finally {
            lock.unlock();
        }
    }
    public static void m1() {
        lock.lock();
        try {
            log.debug("enter m1");
            m2();
        }finally {
            lock.unlock();
        }
    }
    public static void m2() {
        lock.lock();
        try {
            log.debug("enter m2");
        }finally {
            lock.unlock();
        }
    }
}

如果是不可重入锁,第二次获取锁的时候就会被挡住

ReentrantLock可打断

ReentrantLock可打断支持可打断的特性,通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.

在有竞争的情况下,如果该lockinterruptibly()方法没有被其他线程打断,那么和lock方法一样会死等线程释放锁.知道获取到锁

@Slf4j(topic = "c.LockInterruptiblyFeatures")
public class LockInterruptiblyFeatures {
    /**
     * 演示ReentrantLock可打断的特性
     */
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获取到锁,被打断,返回");
                return;
            }
            try {
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        });
        log.debug("获取到锁");
        lock.lock();
        t1.start();
        try{
            log.debug("1s后打断t1");
            //主线程1s后打断t1线程
            Thread.sleep(1000);
            t1.interrupt();
        }finally {
            lock.unlock();
        }
    }
}

通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.

ReentrantLock锁超时

可打断的这个特点是被动地避免死等,由其他线程调用interrupt方法让其不要继续死等.

锁超时是主动地避免死等,在获取锁的过程中,如果其他线程持有者锁一直没有释放,尝试获取锁的线程也不会死等,会设置一个超时时间,如果超过了这个超时时间,如果其他线程仍然没有释放锁,那么当前线程获取锁失败,避免无限制的等待下去,也就避免了死锁.

通过tryLock()方法可以实现锁超时

tryLock()方法不带参数 :-->没有设置超时时间

  • 获取锁成功返回true,获取锁失败返回false

tryLock()方法带参数 :-->表示可以设置超时时间

  • 获取锁成功返回true,如果超过超时的时间还没有获取到锁,就获取锁失败,返回false
@Slf4j(topic = "c.TryLock")
public class TryLock {
    /**
     * 演示ReentrantLock锁超时的情况
     */
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                if(!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("没有获取到锁,立即返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                //tryLock也可以被打断,没有获取到锁
                log.debug("被打断,没有获取到锁,立即返回");
                return;
            }
            log.debug("获取到锁");
            try {
                //执行临界区代码
                log.debug("执行临界区代码");
            }finally {
                //解锁
                lock.unlock();
            }
        },"t1");
        log.debug("获取锁");
        lock.lock();
        t1.start();
        try{
            Thread.sleep(2000);
        }finally {
            log.debug("释放锁");
            lock.unlock();
        }
    }
}

没有超过超时时间获取到锁

超过超时时间没有获取到锁

ReentrantLock解决哲学家就餐问题

package com.example.demo.Controller.DiningPhilosophersProblem;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Philosophers")
public class Philosophers extends Thread{
    private Chopstick left;//左手筷子
    private Chopstick right;//右手筷子

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

    @Override
    public void run() {
        while(true) {
            if(left.tryLock()) {//尝试获取左手筷子
                try{
                    if(right.tryLock()) {//尝试获取到右手筷子
                        try{
                            //既获得了左手筷子,又获得了右手筷子.就可以吃饭了
                            eat();
                        }finally {
                            right.unlock();
                        }
                    }
                    //如果哲学家获取到了左手筷子,但是没有获取到右手筷子
                    //那么哲学家同时也会释放左手筷子
                }finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(200);//思考1s钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分析一下,为啥这样能解决死锁,对于synchronized,也就是没有获取到,就一直死等

而对于ReentrantLock来说,如果没有获取到锁就不等了,防止无限制等待.如果获取到了左手筷子,但没有获取到右手筷子,那么他也会同时释放左手筷子,让其他哲学家先吃

ReentrantLock公平锁

所谓公平锁,也就是先到先得. 如果多个线程获取锁的时候,就会去等待队列EntryList中去排队等待,如果是公平锁也就是排在前面的线程先获取到锁,排在后面的后获取到锁.

而非公平锁,并不是先到先得,而是谁竞争到了谁就先获取到锁.

ReentrantLock条件变量

对于条件变量我们可以理解为 synchronized的WaitSet,也就是条件不满足的时候就调用wait方法去WaitSet中等待,这个WaitSet就相当于条件变量.

对于ReentrantLock来说 ReentrantLock支持多个条件变量,将其条件更加细分

就相当于 :

  • synchronized是那些不满足线程都在一间休息室(同一个WaitSet)中等待.
  • 对于ReentrantLock来说支持多间休息室,有专门等外卖的休息室,有专门等待烟的休息室,唤醒也是按照休息室来唤醒的.

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.TestDemo1")
public class TestDemo1 {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        //要想进入条件变量(消息室) 必须获取到锁
        lock.lock();
        //一个锁对象可以由多个条件变量(多个休息室) 将其细分
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        try{
            //去休息室1 里面等待
            condition1.await();
            
        }finally {
            lock.unlock();
        }
        //唤醒条件变量1(休息室1)的线程
        condition1.signal();
        //唤醒条件变量2(休息室2)所有的线程
        condition1.signalAll();
    }
}

Condition代码练习

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.TestWaitNotify3")
public class TestCondition {
    private static boolean isCigarette = false;
    private static boolean isTakeOut = false;
    //每一个人不用去同一间休息室等了,而是去不同的休息室等待,这样就避免了通一间休息室全部唤醒的问题->虚假唤醒
    private static ReentrantLock ROOM = new ReentrantLock();//一个大房间有多间休息室
    private  static Condition cigaretteWaitSet = ROOM.newCondition();
    private  static Condition takeOutWaitSet = ROOM.newCondition();

    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        cigaretteWaitSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("小南开始干活");
            }finally {
                ROOM.unlock();
            }
        },"小南").start();

        //小女等外卖
        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("是否有外卖 : " +isTakeOut);
                if(!isTakeOut){
                    log.debug("没有外卖,干不了活,等一会");
                    try {
                        takeOutWaitSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("小女干活");
            }finally {
                ROOM.unlock();
            }
        },"小女").start();

        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                isTakeOut = true;
                log.debug("外卖到了");
                takeOutWaitSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送外卖的").start();
        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                isCigarette = true;
                log.debug("烟到了");
                cigaretteWaitSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送烟的").start();
    }
}

对之前的代码做出改进 使用ReentrantLock,这样两个人就不用去同一间休息室等待了,可以去同一个房间的不同休息室等待,这样当唤醒时候,不会把所有人都唤醒.

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

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

相关文章

基于移动最小二乘法的曲线曲面拟合论文阅读笔记

基于移动最小二乘法的曲线曲面拟合论文阅读笔记 论文地址:http://www.cnki.com.cn/Article/CJFDTotal-GCTX200401016.htm 一、Problem Statement 传统的曲线(曲面)拟合方法一般使用最小二乘法, 通过使误差的平方和最小, 得到一个线性方程组&#xff0…

通过alist挂在阿里网盘的方法

1、在github官网https://github.com/alist-org/alist/releases/download/v3.8.0/alist-windows-amd64.zip下载alist软件客户端,双击运行,可以看到默认的密码和服务器地址,打开网页http://localhost:5244/manage/accounts,填写密码…

【Linux】Linux工具

文章目录软件包管理器yumVIM编辑器Linux编译器-gcc/g使用gcc如何完成预处理(进行宏替换)编译(生成汇编)汇编(生成机器课识别代码)链接在这里涉及到一个重要的概念: 函数库函数库一般分为静态库和动态库两种gcc选项gcc选项记忆Linux项目自动化构建工具-make/Makefile背景理解实例…

解决vue中报错 Duplicate keys detected:‘1‘. This may cause an update error.

报错截图: 报错原因: 通过上图的报错信息我们不难看出,报错的主要原因出现在 key 值上,报错的意思大概是检测到重复的 key 值,通俗来讲就是你的 key 值不是唯一的。 解决方案: 问题的根源找到了&#xff…

【初阶数据结构】——链表常见面试题剖析

文章目录前言题目1:移除链表元素题目分析思路1:暴力求解思路2:取非val值尾插至新链表思路讲解思考代码实现不带哨兵位带哨兵位题目2:合并两个有序链表题目分析思路讲解代码实现不带哨兵位带哨兵位题目3:反转链表题目分…

用ode45解微分方程遇到的实际问题

最近在用ode45解微分方程数值解,试图复现论文中的图。一般来说说微分方程(组)只要按照响应的条件去撰写好对应的回调函数即可,基本没什么难度,但对于本文遇到的的这个问题,可能还需要一些技巧去实现解法&am…

动态路由和导航守卫

一、动态路由1、什么是动态路由?将URL地址中可变的内容设置成参数,根据不同的参数渲染不同的组件。(组件可以复用)2、动态路由如何进行参数的传递:(1)如何设置URL地址中的参数:’/ur…

【手写 Vue2.x 源码】第十二篇 - 生成 ast 语法树-流程说明

一,前言 上篇,主要介绍了 vue 数据渲染核心流程,涉及以下几个点: 初次渲染时 template 模板被编译为 ast 语法树;通过 ast 语法树生成 render 函数;通过 render 函数返回 vnode 虚拟节点;使用…

基于K8s的DevOps平台实践(三)

文章目录前言1. Jenkins与k8s集成🍑 插件安装及配置🍑 演示动态slave pod🍑 Pod-Template中容器镜像的制作🍑 实践通过Jenkinsfile实现demo项目自动发布到kubenetes环境2. Jenkins集成Sonarqube🍑 sonarqube架构简介&a…

初阶产品经理必看:如何快速进阶B端产品经理

​从去年开始,大批的互联网企业开始转战B端,众多传统企业也早在几年前就开始向互联网转型。 产业互联网的兴起,让一个岗位的潜藏价值正在逐渐爆发,尤其是富有经验的背景,更加让身价越来越高。 这个岗位就是&#xff…

QProcess的非阻塞式用法以及QApplication::processEvents的使用

一、QProcess的阻塞模式QProcess的应用场景非常广泛。可以使用它在qt程序中执行其他进程,并与之进行通信。当使用它执行一些终端命令和操作时,命令和操作往往是需要一定的时间的,这时QProcess本身提供了方法如:waitForStarted() /…

神经网络自适应PID控制及其应用

神经网络自适应PID控制及其应用 总结来自重庆大学宋永瑞教授2022暑期校园行学术会议 1. 研究背景 目前人工智能的发展为很多领域里的研究提供了可延展性,提供了新的研究问题的思路,无人系统和人工智能正走向深度融合,无人系统里具有核心驱动作…

C语言及算法设计课程实验三:最简单的C程序设计——顺序程序设计(四)

C语言及算法设计课程实验三:最简单的C程序设计——顺序程序设计(四)一、实验目的二、 实验内容2.4、将"China”译成密码三、 实验步骤3.4、顺序程序设计实验题目4:将"China”译成密码的实验步骤3.4.1、变量的定义与赋初…

Android EventBus源码深入解析

前言 EventBus:是一个针对Android进行了优化的发布/订阅事件总线。 github对应地址:EventBus 大家肯定都已经比较熟悉了,这里重点进行源码分析; EventBus源码解析 我们重点从以下三个方法入手,弄清楚register、unre…

关于sql注入这一篇就够了(适合入门)

本文章根据b站迪总课程总结出来,若有不足请见谅 目录 存在sql注入条件 判断数据库类型 注入mysql思路 判断网站是否存在注入点 判断列名数量(字段数) 文件读写操作 网站路径获取方法 注入类型 按注入点数据类型来分类 根据提交方式分类 猜测查询方式 sql…

(Java高级教程)第三章Java网络编程-第四节:TCP流套接字(ServerSocket)编程

文章目录一:Java流套接字通信模型二:相关API详解(1)ServerSocket(2)Socket三:TCP通信示例一:客户端发送什么服务端就返回什么(1)代码(2&#xff0…

量子计算(二十一):Deutsch-Josza算法

文章目录 Deutsch-Josza算法 Deutsch-Josza算法 量子算法是量子计算落地实用的最大驱动力,好的量子算法设计将更快速推动量子计算的发展。 Deutsch-Jozsa量子算法,简称D-J算法,DavidDeutsch和RichardJozsa早在1992年提出了该算法&#xff0…

分布式事务方案分析:两阶段和TCC方案(图+文)

1 缘起 补充事务相关知识过程中, 发现,默认的知识都是基于单体服务的事务,比如ACID, 然而,在一些复杂的业务系统中,采用微服务架构构建各自的业务, 就有了分布式事务的概念,比如&am…

一站式云原生体验|龙蜥云原生ACNS + Rainbond

关于 ACNS 龙蜥云原生套件 OpenAnolis Cloud Native Suite(ACNS)是由龙蜥社区云原生 SIG 推出的基于 Kubernetes 发行版本为基础而集成的套件能力,可以提供一键式部署,开箱即用,以及丰富的云原生基础能力,…

JProfiler的使用

一、安装 从https://www.ej-technologies.com/download/jprofiler/files获取,如果需要对服务器远程分析,注意服务器版本的jprofiler和windows版本一致。 二、监控一个本地进程 2.1 不使用idea 安装之后,打开jprofiler,点击红框…