Java并发-06-AQS(AbstractQueuedSynchronizer)相关

news/2024/5/20 9:11:26 标签: java, 并发, juc, AQS

1-概述

       AQS全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

1.1-主要特点

(1)用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁。其中state的具体定义由子类(开发者)去设计
getState - 获取 state 状态;
setState - 设置 state 状态;
compareAndSetState - cas 机制设置 state 状态;
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。

(2)提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
(3)条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet。

1.2-AQS中重要的方法描述

同步器可重写的方法
方法名称描述
protected boolean tryAcquire(int arg)独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行 CAS 设置同步状态
protected boolean tryRelease(int arg)独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占


 


 


 

实现自定义同步组件时,将会调用同步器提供的模板方法,这些(部分)模板方法与描述如下:

方法名称描述
void acquire(int arg)独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的 tryAcquire(intarg)法
void acquireInterruptibly(int arg)与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException 并返回
boolean tryAcquireNanos(int arglong nanos)在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回 false,如果获取到了返回 true
void acquireShared(int arg)共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg)与acquireShared(int arg)相同,该方法响应中断
boolean tryAcquireSharedNanos(intarg, long nanos)在acquireSharedInterruptibly(intarg)基础上增加了超时限制
boolean release(int arg)独占式的释放同步状态该方法会在释放同步状态之后,将同步队列中第个节点包含的线程唤醒
boolean releaseShared(int arg)共享式的释放同步状态
Collection<Thread> getQueuedThreads)获取等待在同步队列上的线程集合

       同步器提供的模板方法基本上分为3 类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。自定义同步组件将使用同步器提供的模板方法来实现自己的同步语义。

1.3-设计思想

state设计:state 使用 volatile 配合 cas 保证其修改时的原子性;
阻塞恢复设计:park & unpark 来实现线程的暂停和恢复;
队列设计:使用了 FIFO 先入先出队列,并不支持优先级队列;借鉴了 CLH 队列,它是一种单向无锁队列。

2-基于AQS实现自定义锁

2.1-自定义实现不可重入排他锁

基于AQS可以快速实现自定义锁,下面就来实现一个 排他锁,不可重入。

java">public class MyLock1 implements Lock {

     static MySync1 mySync1=new MySync1();


     //尝试 加锁,不成功就进入等待队列
    @Override
    public void lock() {
        mySync1.acquire(1);
    }

    //尝试,不成功,进入等待队列,可打断
    @Override
    public void lockInterruptibly() throws InterruptedException {
        mySync1.acquireInterruptibly(1);
    }

    //尝试一次,不成功返回,不进入队列
    @Override
    public boolean tryLock() {
        return mySync1.tryAcquire(1);
    }

    //尝试,不成功,进入等待队列,有时限
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mySync1.tryAcquireNanos(1,unit.toNanos(time));
    }

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

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


    //独占锁实现tryAcquire,tryRelease,isHeldExclusively
    static class MySync1 extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int acquire) {
            //我们可以设计state 为0 时表示 没有线程占用锁
            if(acquire==1){
                if(compareAndSetState(0,1)){
                    setExclusiveOwnerThread(Thread.currentThread());
                    //state字段是volatile 可以防止指令重排序 所以将线程设置 代码放置 在setState之前
                    setState(1);
                    return true;
                }
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int acquire) {
            //能够进入这个方法表示当前线程肯定已经获取到锁了
            if(acquire==1){
                if(getState()==0){
                    throw new IllegalMonitorStateException();
                }
               setExclusiveOwnerThread(null);
               setState(0);
               return true;
            }
            return false;
        }

        protected Condition newCondition() {
            return new ConditionObject();
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }
    }
}


测试排他性:

java"> MyLock1 myLock1=new MyLock1();

        new Thread(()->{
            myLock1.lock();
            try {
                log.info("t1-lock");
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
                log.error("t1-error");
            }finally {
                myLock1.unlock();
                log.info("t1-unlock");
            }
        },"t1").start();

        new Thread(()->{
            myLock1.lock();
            try {
                log.info("t2-lock");
            }catch (Exception e){
                log.error("t2-error");
            }finally {
                myLock1.unlock();
                log.info("t2-unlock");
            }

        },"t2").start();

控制台输出:

15:36:17.590 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-lock
15:36:18.595 [t2] INFO  com.ycmy2023.aqs.demo01.TestDemo - t2-lock
15:36:18.595 [t2] INFO  com.ycmy2023.aqs.demo01.TestDemo - t2-unlock
15:36:18.595 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-unlock

由此可见:必须当线程1持有锁释放的时候,线程2才能获取到锁

测试不可重入:

java"> MyLock1 myLock1=new MyLock1();

        new Thread(()->{
            myLock1.lock();
            log.info("t1-获取锁1");
            myLock1.lock();
            log.info("t1-获取锁2");
            try {
                log.info("t1-lock");
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
                log.error("t1-error");
            }finally {
                myLock1.unlock();
                log.info("t1-unlock");
            }
        },"t1").start();

控制台输出:

15:38:42.028 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-获取锁1

一直阻塞在第一个日志输出的地方,不会输出第二个日志,说明同一个线程获取到锁,不能再次加锁。

2.2-自定义实现共享锁

      设计一个同步工具:该工具在同一时刻,只允许至多两个线程同时访问,超过两个线程的访问将被阻塞。

分析需求:

(1)确定访问模式。能够在同一时刻支持多个线程的访问,这显然是共享式访问,因此,需要使用同步器提供的acquireShared(int args)方法等和Shared 相关的方法这就要求必须重写 tryAcquireShared(int args)方法和 tryReleaseShared(int args)方法,这样才能保证同步器的共享式同步状态的获取与释放方法得以执行。

(2)定义资源数。在同一时刻允许至多两个线程的同时访问,表明同步资源数为2,这样可以设置初始状态 status 为2,当一个线进行获取,state 减1,该线程释放,则 state加1,状态的合法范围为 0、1和2。其中0表示当前已经有两个线获取了同步资源,此时再有其他线程对同步状态进行获取,该线程只能被阻塞。在同步状态变更时需要使用compareAndSet(int expect,int update)方法做原子性保障。

java">public class MySharedLock implements Lock {

    private static MySync2  sync=new MySync2(2);

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

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

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

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

    @Override
    public Condition newCondition() {
        return null;
    }


    private static class MySync2 extends AbstractQueuedSynchronizer{

        MySync2(int count){
            setState(count);
        }



        @Override
        protected int tryAcquireShared(int reduceCount) {
            for(;;){
                int current=getState();
                int newCount=current-reduceCount;
                if(newCount <0 || compareAndSetState(current,newCount)){
                    return newCount;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int returnCount) {
            for(;;){
                int current=getState();
                int newCount=current+returnCount;
                if(compareAndSetState(current,newCount)){
                    return true;
                }
            }
        }
    }
}

测试开发写的锁

java">  MySharedLock lock=new MySharedLock();

        new Thread(()->{
            lock.lock();
            log.info("t1-获取锁1");
            try {
                log.info("t1-lock");
                TimeUnit.SECONDS.sleep(5);
            }catch (Exception e){
                log.error("t1-error");
            }finally {
                lock.unlock();
                log.info("t1-unlock");
            }
        },"t1").start();


        new Thread(()->{
            lock.lock();
            log.info("t2-获取锁1");
            try {
                log.info("t2-lock");
                TimeUnit.SECONDS.sleep(8);
            }catch (Exception e){
                log.error("t2-error");
            }finally {
                lock.unlock();
                log.info("t2-unlock");
            }
        },"t2").start();


        new Thread(()->{
            lock.lock();
            log.info("t3-获取锁1");
            try {
                log.info("t3-lock");
                TimeUnit.SECONDS.sleep(4);
            }catch (Exception e){
                log.error("t3-error");
            }finally {
                lock.unlock();
                log.info("t3-unlock");
            }
        },"t3").start();

控制台输出:

16:02:50.705 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-获取锁1
16:02:50.705 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-获取锁1
16:02:50.708 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-lock
16:02:50.708 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-lock
16:02:55.709 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-unlock
16:02:55.709 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-获取锁1
16:02:55.709 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-lock
16:02:58.717 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-unlock
16:02:59.716 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-unlock


http://www.niftyadmin.cn/n/5106781.html

相关文章

智能巡检系统有什么特点和功能?它是如何推动企业高效管理?

随着科技的快速发展&#xff0c;智能化管理逐渐成为各行各业的重要发展方向。巡检工作作为企业管理中的重要环节&#xff0c;智能巡检系统综合平台正逐渐成为关注的焦点。本文将从功能、特点和应用场景三个方面&#xff0c;详细解析“的修”工单管理系统的优势和价值。 一、智能…

软件测试/测试开发丨南科大计算机系本科生获“火焰杯”软件测试高校就业选拔赛一等奖

2022年12月2日&#xff0c;计算机系党总支书记、副系主任王琦副教授在工学院南楼551会议室为19级徐驰同学颁发第二届“火焰杯”软件测试开发选拔赛一等奖奖项&#xff0c;为刘烨庞助理教授颁发赛事优秀指导老师奖项。徐驰同学于2022年4月获得该赛事全国总决赛第一名&#xff0c…

面对有挑战的事情

导读 本文内容较多&#xff0c;接近3w字&#xff0c;可以先看目录&#xff0c;再读内容。 以及主要内容为语音转录&#xff0c;口语化内容清理过一波&#xff0c;仍有较多不通顺的地方&#xff0c;可以直接评论指出。 由于本篇文档内容较多&#xff0c;信息量过大&#xff0c…

Apache DolphinScheduler 3.0.0 升级到 3.1.8 教程

安装部署可参考官网 Version 3.1.8/部署指南/伪集群部署(Pseudo-Cluster)https://dolphinscheduler.apache.org/zh-cn/docs/3.1.8/guide/installation/pseudo-cluster 也可以参考我写贴子 DolphinScheduler 3.0安装及使用-CSDN博客DolphinScheduler 3.0版本的安装教程https:…

LeetCode 面试题 10.10. 数字流的秩

文章目录 一、题目二、C# 题解 一、题目 假设你正在读取一串整数。每隔一段时间&#xff0c;你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作&#xff0c;也就是说&#xff1a; 实现 track(int x) 方法&#xff0c;每读入一个数字都会调…

用结构加法比较4个结构的顺序

( A, B )---5-30-2---( 1, 0 )( 0, 1 ) 让网络的输入只有5个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A中有5个点&#xff0c;B全是0。统计迭代次数并排序。 其中5-x有4组数据 5-x 差值结构 迭代次数 41 4-x 差值结构 迭代次数 19 0 0 1 236…

javascript 中 document.getElementsByClassName 和 document.querySelector区别

javascript 中 document.getElementsByClassName 和 document.querySelector区别 document.getElementsByClassName 和 document.querySelector 都是 JavaScript 中常用于获取元素的方法&#xff0c;但它们之间有一些区别。 document.getElementsByClassName 是一个通过 clas…

供应IPQ4018原装芯片

长期供应各品牌原装芯片&#xff1a; AG3331 AG3335MNV AG3352 ALT1250BZ-E0 ALT1250TG-D0 ASR1601 ASR1603E ASR1603S ASR1606C ASR1606L ASR1606S ASR1802S ASR1803E ASR1803S ASR1826 ASR3601 BCM47755 BCM47768 BK7231M CB0201 CB0201L CYW43438 CY…