【并发专题】手写MyReentantLock

news2025/4/14 11:57:54

分析

ReentantLock的特点如下:

  1. 首先是继承自AQS的
  2. 可中断
  3. 可以设置超时时间
  4. 可以切换公平锁/非公平锁
  5. 支持多个条件变量
  6. 支持可重入

事实上,上面的很多东西AQS已经帮忙实现了,所以想要复刻一个不是很难。仔细观察一下源码,我们需要重写的接口只有以下几个:

  1. 新建一个自己的MyReentrantLock类,实现了Lock接口
  2. MyReentrantLock里面,新建一个NonfairSync类,继承自AbstractQueuedSynchronizer
  3. NonfairSync里面需要实现如下接口:

void lock();
boolean tryAcquire();
boolean tryRelease();

至于为什么需要实现这三个接口,看如下注释:
在这里插入图片描述
总的来说:实现lock()是因为我们要实现可重入的话,需要自己写逻辑补充;
实现tryAcquire(),则是因为提供一种机制,让任务尽量可能在进入阻塞队列之前,能获取到锁。因为,进了阻塞队列之后可能会反复阻塞和解除阻塞(经历上下文切换),这个代价是昂贵的;
实现tryRelease(),则是因为这个方法是【释放锁】的核心方法。想要实现可重入的逻辑就得维护这个方法。

源码

为了方便,这里只是简单实现了一个非公平锁的代码。

public class MyReentrantLock implements Lock {

    /**
     * 锁实例
     */
    private NonfairSync sync;

    /**
     * 构造放啊
     */
    public MyReentrantLock() {
        sync = new NonfairSync();
    }

    @Override
    public void lock() {
        sync.lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    /**
     * 非公平锁的实现方式
     */
    public static final class NonfairSync extends AbstractQueuedSynchronizer {

        /**
         * 非公平锁,上锁方法
         */
        public void lock() {

            // CAS操作锁
            boolean result = compareAndSetState(0, 1);
            if (result) {

                // 设置成功,则修改独占状态
                setExclusiveOwnerThread(Thread.currentThread());
            } else {

                // 设置不成功,则准备入等待队列
                acquire(1);
            }
        }

        /**
         * 实现AQS对tryAcquire的方法
         *
         * <p>
         * 以下是AQS的解释:
         * 以独占模式获取,忽略中断。通过调用至少一次tryAcquire实现,成功时返回。
         * 否则,线程将被排队,可能会反复阻塞和解除阻塞,调用tryAcquire直到成功。
         * 这个方法可以用来实现方法Lock.lock。
         * </p>
         */
        @Override
        protected boolean tryAcquire(int acquires) {
            return doTryAcquire(acquires);
        }

        /**
         * 实现AQS的tryRelease方法
         *
         * <p>
         * 通用场景
         * 术语库
         * 尝试将状态设置为在独占模式下反映释放。
         * 此方法总是由执行释放的线程调用。
         * 默认实现抛出UnsupportedOperationException。
         * 参数:
         * Arg -释放参数。该值始终是传递给释放方法的值,或者是进入条件等待时的当前状态值。否则该值是不解释的,可以表示您喜欢的任何内容。
         * 返回:
         * 如果该对象现在处于完全释放状态,则为True,以便任何等待的线程都可以尝试获取;否则为假。
         * 抛出:
         * IllegalMonitorStateException -如果释放会使同步器处于非法状态。此异常必须以一致的方式抛出,才能使同步正常工作。
         * UnsupportedOperationException -如果不支持独占模式
         * </p>
         */
        @Override
        protected boolean tryRelease(int releases) {
            final Thread thread = Thread.currentThread();
            if (thread != getExclusiveOwnerThread()) {
                throw new IllegalMonitorStateException();
            }

            int state = getState();
            int newState = state - releases;

            // 接口要求,完全释放则true,反之false
            boolean fullyRelease = false;
            if (newState == 0) {
                fullyRelease = true;
                setExclusiveOwnerThread(null);
            }

            setState(newState);
            return fullyRelease;
        }

        private ConditionObject newCondition() {
            return new ConditionObject();
        }

        /**
         * 实现非公平模式下的tryAcquire
         */
        private boolean doTryAcquire(int acquires) {
            final Thread currentThread = Thread.currentThread();
            int state = getState();
            if (state == 0) {

                // 再次竞争一下
                boolean result = compareAndSetState(0, acquires);
                if (result) {
                    setExclusiveOwnerThread(currentThread);
                    return true;
                }
            } else {

                // 判断是否为可重入
                Thread exclusiveOwnerThread = getExclusiveOwnerThread();
                if (exclusiveOwnerThread == currentThread) {

                    // 做可重入的逻辑
                    return doReentrantLock(state + acquires);
                }
            }

            return false;
        }

        private boolean doReentrantLock(int newState) {
            if (newState < 0) {
                throw new Error("可重入次数已达上限");
            }
            setState(newState);
            return true;
        }
    }
}

使用示例

public class MyReentrantLockTest {

    // 票数
    public static int tickets = 8;

    // 总人数
    public static final int PERSONS = 10;

    public static final Lock LOCK = new MyReentrantLock();


    public static void main(String[] args) {

        for (int i = 0; i < PERSONS; i++) {
            new Thread(() -> {
                buyTicket();
            }).start();
        }
    }

    public static void main1(String[] args) {
        MyReentrantLock lock = new MyReentrantLock();
        lock.lock();
        try {
            System.out.println("试试看");
            lock.lock();
            try {
                System.out.println("可重入了");
            } finally {
                lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    }


    public static void buyTicket() {
        // 获取锁
        LOCK.lock();
        try {
            Thread.sleep(1000);

            if(tickets > 0) {
                System.out.println("我是" + Thread.currentThread().getName() + ",我来抢第【" + tickets-- + "】张票");
            } else {
                System.out.println("我是" + Thread.currentThread().getName() + ",票卖完了我没抢到");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放说
            LOCK.unlock();
        }
    }

//    系统输出如下:
//    我是Thread-0,我来抢第【8】张票
//    我是Thread-1,我来抢第【7】张票
//    我是Thread-2,我来抢第【6】张票
//    我是Thread-3,我来抢第【5】张票
//    我是Thread-4,我来抢第【4】张票
//    我是Thread-5,我来抢第【3】张票
//    我是Thread-9,我来抢第【2】张票
//    我是Thread-7,我来抢第【1】张票
//    我是Thread-8,票卖完了我没抢到
//    我是Thread-6,票卖完了我没抢到
}

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

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

相关文章

Text-to-SQL小白入门(一)

摘要 本文主要介绍了Text-to-SQL研究的定义、意义、研究方法以及未来展望&#xff0c;主要是对Text-to-SQL领域进行一个初步的认识和了解&#xff0c;适合初学者入门了解。 1 引言 作为Text-to-SQL领域的小白&#xff0c;学习该领域的最好方式就是看最新的综述文章&#xff…

Junit4入门之什么是单元测试?

干了一年多的后端了&#xff0c;从来没有了解过单元测试。虽然我知道测试不仅仅是测试们的任务&#xff0c;后端也要进行自测来保证自己的代码的可用性&#xff0c;但我一直都只是用postman来实施的&#xff0c;调用调通了即可。虽然我也知道Junit是用于测试的软件&#xff0c;…

几种常用到的 Hybrid App 框架方案

移动操作系统在经历了诸神混战之后&#xff0c;BlackBerry OS、Symbian OS、Windows Phone等早期的移动操作系统逐渐因失去竞争力而退出。目前&#xff0c;市场上主要只剩下安卓和iOS两大阵营&#xff0c;使得iOS和安卓工程师成为抢手资源。然而&#xff0c;由于两者系统的差异…

学了python的心得体会200字,学python心得体会1000字

大家好&#xff0c;本文将围绕学了python的心得体会200字展开说明&#xff0c;学python心得体会1000字是一个很多人都想弄明白的事情&#xff0c;想搞清楚学python心得体会800字需要先了解以下几个事情。 一、个人学期总结 本学期在missdu的带领下&#xff0c;进行了python的学…

Jenkins通过OpenSSH发布WinServer2016

上一篇文章> Jenkins集成SonarQube代码质量检测 一、实验环境 jenkins环境 jenkins入门与安装 容器为docker 主机IP系统版本jenkins10.10.10.10rhel7.5 二、OpenSSH安装 1、下载 官网地址&#xff1a;https://learn.microsoft.com/zh-cn/windows-server/administration/op…

Spring Boot的创建和运行

目录 1.Spring Boot的优点 2.Spring Boot项目创建 2.1使用Idea创建 2.2网页版创建 3.项目目录介绍和运行 3.1运行项目 3.2输出 4.注意事项 4.1正确路径 4.2小结&#xff1a;约定大于配置 1.Spring Boot的优点 ●快速集成框架&#xff0c;Spring Boot 提供了启动添…

解决:Uncaught (in promise) SyntaxError: “[object Object]“ is not valid JSON 问题的过程

1、问题描述&#xff1a; 其一、报错为&#xff1a; Uncaught (in promise) SyntaxError: "[object Object]" is not valid JSON 中文为&#xff1a; 未捕获&#xff08;承诺中&#xff09;语法错误&#xff1a;“[object Object]”不是有效的 JSON 其二、问题描…

阿丹千问vue页面升级-使用Markdown形式展示回答--markdown-it库

阿丹&#xff1a; 在之前开发的阿丹千问 发现回复的文章格式使用 Markdown的格式。所以想使用Markdown的方式来给页面来个升级。 下面就是升级以及开发的过程。 升级思路 使用vue中的markdown-it库 在Vue页面中使用Markdown文档 安装markdown-it&#xff1a; 在Vue项目中…

《HeadFirst设计模式(第二版)》第一章源码

代码文件目录结构&#xff1a; FlyBehavior.java package Chapter1_StrategyPattern.ch1_3_behavior.behaviors.fly;public interface FlyBehavior {void fly(); } FlyNoWay.java package Chapter1_StrategyPattern.ch1_3_behavior.behaviors.fly;public class FlyNoWay imp…

使用Jetpack Compose构建时间轴组件的逐步指南

使用Jetpack Compose构建时间轴组件的逐步指南 最近&#xff0c;我们开发一个时间轴组件&#xff0c;显示用户与客户之间的对话。每个对话节点应具有自己的颜色&#xff0c;取决于消息的状态&#xff0c;并且连接消息的线条形成颜色之间的渐变过渡。 我们慷慨地估计了未来的工…

C++学习day--18 空指针和函数指针、引用

1、void 类型指针 void > 空类型 void* > 空类型指针&#xff0c; 只存储地址的值&#xff0c;丢失类型&#xff0c;无法访问&#xff0c;要访问其值&#xff0c;我们必须对这个指 针做出正确的类型转换&#xff0c;然后再间接引用指针 。 所有其它类型的指针都可以隐…

基于C语言 --- 自己写一个扫雷小游戏

C语言程序设计笔记---020 初阶扫雷小游戏(开源)1、arr_main2.c程序大纲2、arr_game2.h3、arr_game2.c3.1、 自定义初化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )3.2、 自定义布置雷函数 SetMine( )3.4、 自定义排查雷函数 FindMine( ) 4、结束语 初阶扫雷小游戏(开…

Redis安装部署(基于windows平台)

redis简介 键值对存储数据库是NoSQL数据库的一种类型&#xff0c;也是最简单的NoSQL数据库。顾名思义&#xff0c;键值对存储数据库中的数据是以键值对的形式来存储的。常见的键值对存储数据库有Redis、Tokyo Cabinet/Tyrant、Voldemort以及Oracle BDB数据库。 Remote Diction…

1.4 信息安全管理

数据参考&#xff1a;CISP官方 目录 信息安全管理基础信息安全管理体系信息安全管理实践 一、信息安全管理基础 1、信息 信息是一种资产&#xff0c;与其他重要的业务资产一样&#xff0c;对组织业务必不可少&#xff0c;因此需要得到适当的保护。 2、信息的价值 企业…

数据结构和算法入门(时间/空间复杂度介绍--java版)

数据结构和算法入门&#xff08;时间/空间复杂度介绍–java版&#xff09; write in front 作者&#xff1a; 向大佬学习 专栏&#xff1a; 数据结构&#xff08;java版&#xff09; 作者简介&#xff1a;大二学生 希望能学习其同学和大佬的经验&#xff01; 本篇博客简介&…

定时任务调度 xxl-job

框架地址 https://gitee.com/jiaruiguo/xxl-job.git项目说明 调度管理系统 xxl-job-admin 定时任务实现系统 普通系统&#xff1a; xxl-job-executor-sample-frameless 微服务系统&#xff1a;xxl-job-executor-sample-springboot 配置说明 工程名&#xff1a;xxl-job-execut…

中国农村程序员学习此【JavaScript教程】购买大平层,开上帕拉梅拉,迎娶白富美出任CEO走上人生巅峰

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 在 Switch 语句添加多个相同选项从函数返回布尔值--聪明方法undefined创建 JavaScript 对象通过点号表示法访问对象属性使用方括号表示法访问对象属性通过变量访问对象属性给 JavaScript 对象添加新属性删除…

AD21 PCB设计的高级应用(八)Draftsman的应用

&#xff08;八&#xff09;Draftsman的应用 1.创建Draftsman文档2.Draftsman页面选项设置3.放置绘图数据3.1 装配图3.2 板制造图3.3 钻孔图和钻孔列表3.4 图层堆栈图例3.5 BOM3.6 标注、注释、测量尺寸 4.文档输出4.1 打印或者导出为PDF4.2 添加到Output job Draftsman 是为电…

windows基础命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一.目录和文件的操作 1.cd 命令 切换到d盘 2.目录分为相对路径和绝对路径 3. dir命令 用于显示目录和文件列表 4. md 或 mkdir 创建目录 5. rd 用于删…