【深入浅出Java并发编程指南】「实战篇」教你如何使用AbstractQueuedSynchronizer实现自己的同步器组件

news2024/12/24 8:50:52

前提概要

之前的文章中会涉及到了相关AQS的原理和相关源码的分析,所谓实践是检验真理的唯一标准!接下来就让我们活化一下AQS技术,主要针对于自己动手实现一个AQS同步器。

定义MyLock实现Lock

Doug Lea大神在JDK1.5编写了一个Lock接口,里面定义了实现一个锁的基本方法,我们只需编写一个MyLock类实现这个接口就好。

下面就是针对于Lock实现类的基本基础架构机制,从而实现对应的各个核心的方法内容。

class MyLock implements Lock {
}

对应的Lock接口的方法实现

加锁

如果加锁竞争锁资源不成功则进入等待队列。

 @Override
public void lock() {}

加锁

如果加锁竞争锁资源不成功则进入等待队列,在处于等待过程中是允许外界调用interrupt进行中断等待状态。

@Override
public void lockInterruptibly() throws InterruptedException {}

尝试加锁

与try的区别就是try会因为没有获取到锁资源而进入等待队列。而对于tryLock,如果出现失败后,则会立刻返回不会进入等待队列,而是返回给调用端false,如果获取到资源则进行返回true,有一点乐观锁的感觉。

@Override
public boolean tryLock() {}

尝试加锁 带超时的

他会尝试阻塞和等待对应的时间time和对应的TimeUnit时间戳单位,进行等待加锁。

@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {}

释放锁

@Override
public void unlock() {}
返回一个条件变量(不在本案例谈论)
@Override
public Condition newCondition() {}

定义好MyLock后,接下来就是实现各个方法的逻辑,达到真正的用于线程间sync互斥的需求。

自定义一个同步器

我们定义MySync然后继承自AbstractQueuedSynchronizer的Sync对象类。实现自定义的MySync前,先了解AQS内部的一些基本概念。在AQS中主要的一些成员属性如下:

  • state:用于标记资源状态,如果为0表示资源没有被占用,可以加锁成功。如果大于0表示资源已经被占用,然后根据自己的定义去实现是否允许对共享资源进行操作

    • 比如:ReentrantLock的实现方式是当state大于0,那么表示已经有线程获得锁了,我们都知道ReentrantLock是可重入的,其原理就是当有线程次进入同一个lock标记的临界区时。先判断这个线程是否是获得锁的那个线程,如果是,state会+1,此时state会等于2。
    • 当unlock时,会一层一层的减1,直到state等于0则表示完全释放锁成功。
  • head、tail:用于存放获得锁失败的线程。在AQS中,每一个线程会被封装成一个Node节点,这些节点如果获得锁资源失败会链在head、tail中,成为一个双向链表结构。

  • exclusiveOwnerThread用于存放当前获得锁的线程,正如在state说明的那样。ReentrantLock判断可重入的条件就是用这个exclusiveOwnerThread线程跟申请获得锁的线程做比较,如果是同一个线程,则state+1,并重入加锁成功

知道这些概念后我们就可以自定义一个AQS:

public final class MySync extends AbstractQueuedSynchronizer {
    /**
    * 尝试加锁
    */
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) {
            // 修改state状态成功后设置当前线程为占有锁资源线程
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    /**
    * 释放锁
    */
    @Override
    protected boolean tryRelease(int arg) {
        setExclusiveOwnerThread(null);
        // state有volatile修饰,为了保证解锁后其他的一些变量对其他线程可见,把setExclusiveOwnerThread(null)放到上面 happens-before中定义的 volatile规则
        setState(0);
        return true;
    }
    /**
    * 判断是否是独占锁
    */
    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }
}

将MySync组合进MyLock

最后一步就是将第一步中的所有方法逻辑完成

class MyLock implements Lock {

    // 组合自定义sync器
    private MySync sync = new MySync();

    /**
     * 加锁。如果不成功则进入等待队列
     */
    public void lock() {
        sync.acquire(1);
    }
    /**
    * 加锁(可被interrupt)
    */
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    /**
     * 尝试加锁
     */
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    /**
     * 加锁 带超时的
     */
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toMillis(time));
    }
    /**
    * 释放锁
    */
    public void unlock() {
        sync.release(0);
    }
    /**
    * 返回一个条件变量(不在本案例谈论)
    */
    @Override
    public Condition newCondition() {
        return null;
    }
}

完成整个MyLock的逻辑后,发现在lock()、unlock()中调用的自定义sync的方法tryAcquire()和tryRelease()方法。我们就以在lock()方法中调用acquire()方法说明模板设计模式在AQS中的应用。

点进.acquire()方法后,发现改该方法是来自AbstractQueuedSynchronizer中:

  • 在这里面可以看到tryAcquire方法,继续点进去看看tryAcquire(),发现该方法是一个必须被重写的方法,否则抛出一个运行时异常。

  • 模板方法设计模式在这里得以体现,再回到我们第二部中自定义的MySync中,就是重写了AQS中的tryAcquire()方法。

因此整个自定义加锁的流程如下:

  • 调用MyLock的lock(),lock()方法调用AQS的acquire()方法
  • 在acquire()方法中调用了tryAcquire()方法进行加锁
  • 而tryAcquire()方法在AQS中是一个必须让子类自定义重写的方法,否则会抛出一个异常
  • 因此调用tryAcquire()时实际上是调用了我们自定义的MySync类中tryAcquire()方法

总结

AQS作为Java并发体系下的关键类,在各种并发工具中都有它的身影,如ReentrantLock、Semaphore等。这些并发工具用于控制sync互斥的手段都是采用AQS,外加Cas机制。AQS采用了模板方法设计模式让子类们自定义sync互斥的条件,比如本案例中MySync类重写了tryAcquire方法。

下面实现一个自定义的sync:

public class SelfSynchronizer {

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

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

    public boolean unLock() {
        return sync.release(1);
    }

    static class Sync extends AbstractQueuedSynchronizer {
        //是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        /**
         * 获取sync资源
         * @param acquires
         * @return
         */
        @Override
        public boolean tryAcquire(int acquires) {
            if(compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            //这里没有考虑可重入锁
            /*else if (Thread.currentThread() == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }*/
            return false;
        }

        /**
         * 释放sync资源
         * @param releases
         * @return
         */
        @Override
        protected boolean tryRelease(int releases) {
            int c = getState() - releases;
            boolean free = false;
            if (c == 0) {
                free = true;
            }
            setState(c);
            return free;
        }
    }
}

ReentrantLock源码和上面自定义的sync很相似,测试下该sync,i++在多线程下执行情况:

public class TestSelfSynchronizer {
    private static int a = 0;
    private static int b = 0;
    private static SelfSynchronizer selfSynchronizer = new SelfSynchronizer();
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 50, 1, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>());
    private static ExecutorService ec = Executors.newFixedThreadPool(20);
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20 ; i++) {
            executor.submit(new Task());
        }
        for (int j = 0; j < 20 ; j++) {
            ec.submit(new TaskSync());
        }
        Thread.sleep(10000);
        System.out.println("a的值:"+ a);
        System.out.println("b的值" + b);
        executor.shutdown();
        ec.shutdown();
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            for(int i=0;i<10000;i++) {
                a++;
            }
        }
    }
    static class TaskSync implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
            	//使用sync器加锁
                selfSynchronizer.lock();
                b++;
                selfSynchronizer.unLock();
            }
        }
    }
}

开启两个线程池,对int型变量自增10000次,如果不加sync器,最后值小于200000,使用了自定义sync器则最后值正常等于200000,这是因为每次自增操作加锁

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

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

相关文章

【C++】模拟实现STL容器:vector

目录 一、vector迭代器失效问题 1、Visual Studio和g对迭代器失效问题的表现 2、解决迭代器失效的方法 二、模拟实现构造函数调用不明确 1、问题描述 2、解决调用不明确的方法 三、reserve中的深浅拷贝问题 1、reserve中浅拷贝发生原因 2、浅拷贝发生的图解 3、解决方…

vue.js毕业设计,基于vue.js前后端分离外卖点餐系统设计与实现(H5移动项目)

功能介绍 【后台管理员功能】 会员列表&#xff1a;查看所有注册会员信息&#xff0c;支持删除 录入资讯&#xff1a;录入资讯标题、内容等信息 管理资讯&#xff1a;查看已录入资讯列表&#xff0c;支持删除和修改 广告设置&#xff1a;上传图片和设置小程序首页轮播图广告地…

用Python代码画世界杯吉祥物拉伊卜(附代码)

用Python代码画世界杯吉祥物拉伊卜&#xff08;附代码&#xff09; 世界杯正在火热进行中&#xff0c;世界杯的吉祥物拉伊卜也非常火。 本文用Python代码画世界杯吉祥物。不废话&#xff0c;可以直接先看视频效果。 视频效果 用Python代码画世界杯吉祥物拉伊卜实现方法介绍 …

运用滤波反投影的方法对图像进行重建matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法描述 直接由正弦图得到反投影图像&#xff0c;会存在严重的模糊&#xff0c;这是早期 CT 系统所存在的问题。傅立叶中心切片定理表明&#xff0c;投影的一维傅立叶变换是得到投影区域的二维傅…

360安全卫士弹窗广告怎么彻底关闭

如何关闭360广告弹窗&#xff1f;有时候我们在电脑上看一些视频或者整理一些文件时&#xff0c;经常莫名其妙会出现一些广告弹窗&#xff0c;即使是关了也还会出现&#xff0c;很是影响用户体验感&#xff0c;那么怎么彻底关闭呢&#xff1f;下面给大家介绍具体教程&#xff0c…

【单目3D目标检测】GUPNet论文精读与代码解析

文章目录PrefaceAbstractContributionsPipelineBackboneNeckHeadLossGUPIn PaperIn CodeHTLIn PaperIn CodeRefernecePreface Lu Y, Ma X, Yang L, et al. Geometry uncertainty projection network for monocular 3d object detection[C]. Proceedings of the IEEE/CVF Intern…

ActivitiListener

ActivitiListener目录概述需求&#xff1a;设计思路实现思路分析1.ActivitiListener2.Activity3.Gateway5.FieldExtensionIOSpecification参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip ha…

骨传导原理是什么?哪些骨传导耳机值得入手

​骨传导耳机是目前耳机市场比较流行耳机&#xff0c;深受年轻一族和运动达人的喜爱。但尽管这种产品受到很多人的青睐&#xff0c;相较传统耳机&#xff0c;大众对骨传导耳机的认识和程度并不高&#xff0c;也有很多小伙伴不知道骨传导耳机的原理是怎么发声的&#xff0c;骨传…

Vue子组件传自定义属性给父组件

我们知道组件之间是不能够之间进行通信的&#xff0c;都是相互独立的&#xff0c;你用不了我的状态和方法&#xff0c;我也用不了你的&#xff0c;那如何实现通信呢&#xff0c;可以间接实现&#xff1b; 实现父组件和子组件的通信&#xff1a; 子组件想用父组件的状态需要父…

Java并发之线程池

文章目录前言一、Java中线程池概览1.1 类图1.2 内部流程图二、源码探索2.1 构造参数2.2 线程池状态2.3 Worker 的添加和运行2.4 阻塞队列2.5 任务拒绝策略三、实际使用3.1 动态线程池3.2 拓展使用3.3 springboot 中线程池参考前言 在高并发的 Java 程序设计中&#xff0c;编写…

数字化安全生产平台 DPS 重磅发布

11 月 5 日&#xff0c;在 2022 杭州 云栖大会上&#xff0c;数字化安全生产平台 DPS 重磅发布&#xff0c;助力传统运维向 SRE 转型。 阿里巴巴资深技术专家 周洋 十四五规划下&#xff0c;各行各业全面加速数字化转型与升级。随着企业数字化业务规模变大&#xff0c;迭代速…

Dubbo服务远程调用的简介及使用教程

一、Dubbo的简介 Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架。 致力于提供高性能和透明化的 RPC 远程服务调用方案&#xff0c;以及 SOA 服务治理方案。 官网&#xff1a;https://dubbo.apache.org/ SOA架构&#xff1a;&#xff08;Service-Oriented Arch…

华为云RDS数据库测评:性能超出预期,双11优惠还在继续

一、前言 作为一名电商行业公司的员工&#xff0c;深刻体会到系统大压力、高并发下保证服务的正常使用是多么严峻的挑战。双11这段时间&#xff0c;因为激增的使用量让我们的数据库服务严重吃紧&#xff0c;压力特别的大&#xff0c;甚至还出现了交易漏单&#xff0c;脏数据等…

【Servlet】3:Servlet 的基本原理、Servlet对象的生命周期

目录 第五章 | 动态资源与Servlet | 章节概述 | Tomcat与Servlet的 原理、关系 Tomcat的基本构成​编辑 Server处理HTTP请求 Connector内部架构分析 Container内部架构分析 Tomcat的执行流程小结 | Servlet 概述、接口实现 Servlet的基本概述 实现Servlet接口并通过U…

LeetCode HOT 100 —— 10.正则表达式匹配

题目 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符 ‘*’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 思路 对于字符串…

11月24日国产蓝牙AOA高精度定位vs国外知名厂家的蓝牙aoa定位效果的展示

11月24日国产蓝牙AOA高精度定位vs国外知名厂家的蓝牙aoa定位效果的展示 11月24日国产蓝牙AOA高精度定位vs国外知名厂家的蓝牙aoa定位效果的展示

操作系统的基本概念

文章目录一、操作系统的概念1.什么是操作系统&#xff1f;2 计算机系统的构成3 系统软件的概念4 操作系统的主要作用二、操作系统目标和功能1. 目标1.1 有效性1.2 方便性1.3 可扩充性1.4 开放性2. 功能2.1 作为系统资源的管理者2.2 作为用户与计算机[硬件系统]之间的接口2.3 实…

Linus 文件处理(一)

目录 一、前言 二、低级文件访问 1、write 2、read 3、open 4、Initial Permissions &#xff08;1&#xff09;umask &#xff08;2&#xff09;close &#xff08;3&#xff09;ioctl &#xff08;4&#xff09;第一个 copy_system.c 程序 &#xff08;5&#xff…

Apache ShardingSphere(一) 基本概念介绍

文章目录一 基本介绍1.1 概述1.2 ShardingSphere JDBC1.3 ShardingSphere Proxy1.4 ShardingSphere Sidecar1.5 数据库的扩展1.5.1 向上扩展1.5.2 横向扩展1.5.2.1 读写分离1.5.2.2 垂直切分1.5.2.3 水平切分1.6 分库与分表1.6.1 水平分库1.6.2 水平分表1.6.3 垂直分库1.6.4 垂…

[iOS]使用MonkeyDev完成Hook

一、确定目标 先定个小目标&#xff0c;使用七猫举个例&#xff0c;去移除小说阅读页底部广告和章节之间的广告。 二、HOOK 1. 创建MonkeyApp项目导入砸壳包 2. 使用Reveal工具确定“底部广告”和“章末广告”的视图名称 底部广告 View Controller: Class: QMReader.YYReade…