深入理解aqs(代码片段)

z啵唧啵唧 z啵唧啵唧     2022-12-04     509

关键词:

文章目录

深入理解AQS

AQS

概念
  • 是一种阻塞式锁和相关的同步器工具的框架
特点
  • 用state属性来表示资源的状态(分为独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取和释放锁
    • getState获取state状态
    • setState设置state状态
    • compareAndSetState 利用cas机制设置state状态
    • 独占模式是只有一个线程能够访问资源,共享模式是可以允许多个线程访问资源
  • 提供了基于队列的等待队列,类似于Monitor的EntryList
  • 条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet

AOS自定义实现锁

package com.zb.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Description:
 * @Author:啵啵啵啵啵啵唧~~~
 * @Date:2022/4/26
 */
@Slf4j(topic = "c.TestAqs")
public class TestAqs 


/**
 * 自定义锁(不可重入锁)
 */
class MyLock implements Lock 
    /**
     * 独占锁
     */
    class MySync extends AbstractQueuedSynchronizer
        /**
         * 加锁
         * @param arg
         * @return
         */
        @Override
        protected boolean tryAcquire(int arg) 
            if (compareAndSetState(0,1))
                //加上了锁,需要设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            
            return false;
        

        /**
         * 解锁
         * @param arg
         * @return
         */
        @Override
        protected boolean tryRelease(int arg) 
            //将Qwn设置为空表示没有线程占用
            setExclusiveOwnerThread(null);
            //将状态改为0
            setState(0);
            return true;
        

        /**
         * 是否持有独占锁
         * @return
         */
        @Override
        protected boolean isHeldExclusively() 
            return getState()==1;
        

        public Condition newCondition() 
            return new ConditionObject();
        
    

    private MySync sync = new MySync();

    /**
     * 加锁,不成功进入等待队列等待
     */
    @Override
    public void lock() 
       sync.acquire(1);
    

    /**
     * 加锁,可打断
     */
    @Override
    public void lockInterruptibly() throws InterruptedException 
       sync.acquireInterruptibly(1);
    

    /**
     * 尝试加锁只加锁一次一次失败之后就返回false
     */
    @Override
    public boolean tryLock() 
        return sync.tryAcquire(1);
    

    /**
     * 带超时版本的tryLock
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException 
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    

    /**
     * 解锁
     */
    @Override
    public void unlock() 
       sync.release(1);
    

    /**
     * 条件变量
     * @return
     */
    @Override
    public Condition newCondition() 
        return sync.newCondition();
    


ReentrantLock原理

非公平锁实现原理
加锁解锁原理
  • 先看构造器,默认为非公平锁的实现
/**
 * Creates an instance of @code ReentrantLock.
 * This is equivalent to using @code ReentrantLock(false).
 */
public ReentrantLock() 
    //NonfairSync继承自AQS
    sync = new NonfairSync();

  • 非公平锁加锁源码
final void lock() 
    if (compareAndSetState(0, 1))
        //没有竞争的时候状态为1,进行加锁->将owner设置为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);

  • 没有竞争的时候,就加锁成功了
竞争失败原理
  • 出现竞争的时候
final void lock() 
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //出现竞争的时候即当前的状态不为1->调用这个acquire()方法
                acquire(1);
        
  • acquire()方法
public final void acquire(int arg) 
       //acquire()方法会调用一个tryAcquire(arg)方法其实就是尝试获取锁,
        if (!tryAcquire(arg) &&
            //如果再调用这个tryAcquire(arg)方法的时候其他线程恰好释放了锁,那么tryAcquire(arg) 方法的返回值就是false就不会走这个if块,否者就会走这个if语句块,执行acquireQueued()方法这个方法的作用式添加一个节点进入阻塞
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    

  • 如果尝试获取锁失败会进入acquireQueued()方法在这个方法里面其实还会再尝试获取几次
/**
当前线程进入acquireQueued的逻辑
1、acquireQueued会在一个死循环中不断尝试获取锁,失败之后进入park阻塞
2、如果自己是紧邻着head那么自己排在第二位,此时尝试再次获取锁,如果这时恰好人家咱占有者释放锁成功,那么自己就有机会获得锁,如果人家没有释放锁释放锁,就还是获取失败
3、获取失败进入shouldParkAfterFailedAcquire逻辑将前驱node,即head的waitStatus改为-1,改为-1的意思是前驱节点需要唤醒这个后继节点,然后返回false再次尝试
4、shouldParkAfterFailedAcquire执行完毕之后回到这个acquireQueued,再次尝试tryAcquire,如果失败
5、这时候再次进入shouldParkAfterFailedAcquire方法时,因为前驱节点waitStatus已经时-1了,这次返回true
6、shouldParkAfterFailedAcquire返回true就会进入到这个parkAndCheckInterrupt方法,进行park
**/

//--------------------------------------------------------------------------------
//这就是等待对列 
final boolean acquireQueued(final Node node, int arg) 
        boolean failed = true;
        try 
            boolean interrupted = false;
            for (;;) 
                //获取前驱节点
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) 
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            
         finally 
            if (failed)
                //不再进行尝试
                cancelAcquire(node);
        
    

//------------------------------------------------------------------------------------
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) 
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * 该节点已经设置了请求释放信号的状态,因此可以安全停止
             */
            return true;
        if (ws > 0) 
            /*
             * 前任被取消了。跳过前导并指示重试。
             */
            do 
                node.prev = pred = pred.prev;
             while (pred.waitStatus > 0);
            pred.next = node;
         else 
            /*
             * waitStatus必须为0或0。表明我们需要信号,但先别停车。打电话的人需要重试以确保在停车前无法获取。
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        
        return false;
    

//---------------------------------------------------------------------------------------
   private final boolean parkAndCheckInterrupt() 
        LockSupport.park(this);
        return Thread.interrupted();
    
RenntrantLock可重入的原理
  • nonfairTryAcquire()获取锁源码分析
 //acquires 为1
final boolean nonfairTryAcquire(int acquires) 
     //先获取当前的线程
            final Thread current = Thread.currentThread();
     //获取当前线程的状态
            int c = getState();
     //判断当前线程的状态是为0,为0表示没有线程占用
            if (c == 0) 
                //没有线程占用的话直接使用CAS进行交换,此时状态就改变为1
                if (compareAndSetState(0, acquires)) 
                    //设置当前锁的占有者为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                
            
    //如果状态不为0,表示当前锁还被占有着呢,此时线程比一定会被阻塞住,他会先判断锁的占有者是否是自己
            else if (current == getExclusiveOwnerThread()) 
                //如果此时锁的占有者就是自己,那么将state这个状态进行+1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            
            return false;
        
可打断原理
可打断模式
  • 在此模式下,即使他被打断,仍然会被驻留在AQS对罗列中,等待获得锁之后才能继续运行
  • 线程在没有办法获得锁的时候会进入这个acquireQueued()方法进行循环尝试
  • acquireQueued方法分析
private final boolean parkAndCheckInterrupt() 
    //如果打断标记已经是true,则park会失效
        LockSupport.park(this);
    //interrupted 会清除打断标记
        return Thread.interrupted();
    


final boolean acquireQueued(final Node node, int arg) 
        boolean failed = true;
        try 
            boolean interrupted = false;
            for (;;) 
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) 
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //尝试仍不成功进入这个park方法
                    parkAndCheckInterrupt())
                    interrupted = true;
            
         finally 
            if (failed)
                cancelAcquire(node);
        
    
公平锁实现原理
非公平锁实现
        final boolean nonfairTryAcquire(int acquires) 
            final Thread current = Thread.currentThread();
            int c = getState();
            //判断是否有获得锁
            if (c == 0) 
                //没有线程获得这个把锁的时候,当前线程直接尝试CAS获得,不会去检查AQS队列,所以是非公平的
                if (compareAndSetState(0, acquires)) 
                    setExclusiveOwnerThread(current);
                    return true;
                
            
            else if (current == getExclusiveOwnerThread()) 
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            
            return false;
        
公平锁实现
  • 公平锁和非公平锁区别主要在于 tryAcquire方法的实现
  • 公平锁在线程进来之后会先判断AQS队列当中是否有这个前序节点,没有才会去竞争

读写锁

ReentrantReadWriteLock
  • 当读操作远远高于写操作时,这时候使用读写锁让读读可以并发,提高性能。
  • 类似于数据库当中的select … from … lock in share mode
  • 提供一个 数据容器类 内部分别使用读锁保护数据的read()方法,写锁保护数据的write()方法
/**
 * 对数据进行一些读写实验
 */
@Slf4j(topic = "c.DataContainer")
class DataContainer
    /**
     * 读写操作的数据
     */
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    /**
     * 获取读锁对象
     */
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    /**
     * 获取写锁对象
     */
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    /**
     * 读取数据操作
     * @return
     */
    public Object read()
        log.debug("获取读锁");
        r.lock();
        try 
            log.debug("读取");
            Thread.sleep(1000L);
         catch (InterruptedException e) 
            e.printStackTrace();
         finally 
            log.debug("释放读锁");
            r.unlock();
            return data;
        
    

    /**
     * 写数据操作
     * @param data
     */
    public void write(Object data)
        log.debug("获取写锁");
        w.lock();
        try 
            log.debug("写入");
            this.data = data;
        finally 
            log.debug("释放写锁");
            w.unlock();
        
    


  • 验证读读不互斥
//开两个线程同时调用对这个数据进行读操作
@Slf4j(topic = "c.TestReadWriteLock")
public class TestReadWriteLock 
    public static void main(String[] args) 
        DataContainer dc = new DataContainer();
        new Thread(()->
            dc.read();
        ,"t1").start();
        new Thread(()->
            dc.read();
        ,"t2").start();
    

读读并不会互斥

  • 验证读写操作的互斥
@Slf4j(topic = "c.TestReadWriteLock")
public class TestReadWriteLock 
    public static void main(String[] args) throws InterruptedException 
        DataContainer dc = new DataContainer();
        new Thread(()->
            dc.read();
        ,"t1").start();
        Thread.sleep(100L);
        new Thread(()->
            dc.write(1);
        ,"t2").start();
    

读写互斥

注意事项
  • 读写锁不支持条件变量
  • 重入时升级不支持,即持有读锁的情况下去获取写锁,会导致读写锁永久的等待
  • 重入时降级时支持的,意思就是持有写锁的情况下去获取读锁是支持的
class CachedData 
    Object data;
    // 是否有效,如果失效,需要重新计算 data
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() 
        rwl.readLock().lock();
        if (!cacheValid) 
// 获取写锁前必须释放读锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try 
// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
                if (!cacheValid) 深入理解abstractqueuedsynchronizer(代码片段)

...么要维护2个队列呢?一个同步队列不够用嘛?3.深入理解条件等待队列4.深入理解同步等待队列        问题3:处于阻塞等待的线程是如何在同步队列里被唤醒的?5.条件队列转同步等待队列 小结什么是AQS?     ... 查看详情

9.深入理解abstractqueuedsynchronizer(aqs)(代码片段)

1.AQS简介 在上一篇文章中我们对lock和AbstractQueuedSynchronizer(AQS)有了初步的认识。在同步组件的实现中,AQS是核心部分,同步组件的实现者通过使用AQS提供的模板方法实现同步组件语义,AQS则实现了对同步状态的管理,以及对... 查看详情

进阶笔录-深入理解java线程之-aqs(代码片段)

AQS原理析什么是AQS?java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,... 查看详情

java锁深入理解5——共享锁(代码片段)

前言本篇博客是《Java锁深入理解》系列博客的第五篇,建议依次阅读。各篇博客链接如下:Java锁深入理解1——概述及总结Java锁深入理解2——ReentrantLockJava锁深入理解3——synchronizedJava锁深入理解4——ReentrantLockVSsynchroniz... 查看详情

abstractqueuedsynchronizer(aqs)抽丝剥茧深入了解juc框架原理(代码片段)

目录简介Lock简单实用主体框架原理解析独占锁AQS数据结构CLH数据结构acquire实现步骤addWaiteracquireQueuedshouldParkAfterFailedAcquireparkAndCheckInterruptcancelAcquireunparkSuccessoracquirereleasetryRelease共享锁获取共享锁doAcquireSharedsetHea 查看详情

abstractqueuedsynchronizer(aqs)抽丝剥茧深入了解juc框架原理(代码片段)

文章目录简介Lock简单实用主体框架原理解析独占锁AQS数据结构CLH数据结构acquire实现步骤addWaiteracquireQueuedshouldParkAfterFailedAcquireparkAndCheckInterruptcancelAcquireunparkSuccessoracquirereleasetryRelease共享锁获取共享锁doAcquireSharedsetH 查看详情

聊聊高并发(二十四)解析java.util.concurrent各个组件深入理解aqs

近期总体过了下AQS的结构。也在网上看了一些讲AQS的文章,大部分的文章都是泛泛而谈。又一次看了下AQS的代码,把一些新的要点拿出来说一说。AQS是一个管程。提供了一个主要的同步器的能力,包括了一个状态,改动状态的原... 查看详情

从reentrantlock的实现看aqs的原理及应用(代码片段)

...线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理层,并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁的知识点,同时采取问答的模式来帮助大家理解AQS。由于篇幅原... 查看详情

源码解析之aqs源码解析(代码片段)

要理解Lock首先要理解AQS,而要理解并发类最好的方法是先理解其并发控制量不同值得含义以及该类运作流程,然后配合一步步看源码。该类有一个重要的控制量是WaitStates。/**waitStatusvaluetoindicatethreadhascancelled*/staticfinalintCANCELLED=1;/... 查看详情

jdk源码分析深入源码分析countdownlatch(代码片段)

前言CountDownLatch是一个闭锁实现,它可以使一个或者多个线程等待一组事件发生。它包含一个计数器,用来表示需要等待的事件数量,coutDown方法用于表示一个事件发生,计数器随之递减,而await方法等待计数器为0之前一直阻塞... 查看详情

深入java并发包源码aqs的介绍与使用

深入java并发包源码(一)简介深入java并发包源码(二)AQS的介绍与使用深入java并发包源码(三)AQS独占方法源码分析AQS本文章会讲解AQS的使用方法,然后通过DEBUG跟踪AQS执行的一系列操作来分析源码,读者跟着文章DEBUG跟踪源码能更容... 查看详情

aqs源码的简单理解(代码片段)

概念AQS全称AbstractQueuedSynchronizer。AQS是一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。ReentrantLock、Semaphore、CountDownLatch、FutrueTask,这些都是基于AQS构建... 查看详情

aqs源码解析(代码片段)

...。这样之后再翻看以AQS为基础的各种各样的锁实现就会好理解的多了。我们结合着源码文件中的注释来看下。源代码英文注释:/***Providesaframeworkfo 查看详情

aqs解析与实战(代码片段)

...基于AQS原理的几个核心点,谈谈对AbstractQueuedSynchronizer的理解,并实现一个自定义同步器。AQS原理面试题的核心回答要点state状态的维护。CLH队 查看详情

从reentrantlock加锁解锁角度分析aqs(代码片段)

本文用于记录在学习AQS时,以ReentrantLock为切入点,深入源码分析ReentrantLock的加锁和解锁过程。同步器AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态(通常锁或者同步组件内部会实现一个Sync... 查看详情

juc同步器框架abstractqueuedsynchronizer源码图文分析(代码片段)

....com上可以找到相关的译文(《JUC同步器框架》),如果想要深入研究AQS必须要理解一下该论文的内容,然后详细分析一下AQS的源码实现。本文在阅读AQS源码的时候选用的JDK版本是JDK11。原文:JUC同步器框架AbstractQueuedSynchronizer源码... 查看详情

深入浅出java并发编程指南「原理分析篇」非常全面的的探索和分析aqs的基本原理和实现机制(道法器术篇)(代码片段)

🐉大致介绍本章讲解一下CAS,本质就是机器指令:cmpxchg+lock(根据处理器核数进行判断)原子操作;而在谈到并发操作里面,我们不得不谈到AQS,JDK的源码里面好多并发的类都是通过Sync(同... 查看详情

从reentrantlock的实现看aqs的原理及应用(代码片段)

...态State2.2AQS重要方法与ReentrantLock的关联2.3通过ReentrantLock理解AQS2.3.1线程加入等待队列2.3.1.1加入队列的时机 查看详情