java asq_Java并发:AQS原理

本文探讨了Java并发包JUC中的核心类AbstractQueuedSynchronizer(AQS),它是许多并发工具类如ReentrantLock、CountDownLatch等的基础。AQS通过同步队列和同步状态管理线程同步,ReentrantLock的lock()和unlock()方法基于AQS实现。文章详细分析了AQS在ReentrantLock中的应用,包括非公平锁的加锁和解锁过程。
摘要由CSDN通过智能技术生成

J·U·C Java并发包

JUC 是java.util.concurrent的简称,在jdk 1.5时被引入,JUC包的主要作者是并发大师Doug Lea,包含了非常多的并发编程工具类,其中有一个叫做AbstractQueuedSynchronizerd的抽象类,这个类通常被称为AQS,这是一个非常重要的类,可以说是整个JUC并发包的根基之一,它抽象了与线程同步和协作相关的最核心逻辑,很多并发工具类都是基于AQS来实现的,比如最常用的ReentrantLock、CountDownLatch、Semaphore等等。

AQS的核心是两个双向链表队列(同步队列和等待队列)和一个同步状态,同步队列用来记录获取同步状态失败的线程,等待队列用来获取在Condition接口上调用await()方法的线程,同步状态是一个int类型的变量,在不同的实现中有不同的含义。下面以ReentrantLock的独占锁为例来聊聊AQS的原理,关于共享模型、可中断模式、超时模式的实现原理,实际上本质区别不大,限于篇幅,后面再说。

ASQ在ReentrantLock中的应用

ReentrantLock是一种显示锁,作用与synchronized一样,用来做资源的同步,保证共享资源的线程安全,使用范式如下:

Lock lock = new ReentrantLock();lock.lock();try {  do something}finally {  lock.unlock();}

ReentrantLock有两种模式,一种是公平模式,一种是非公平模式,为了不引入其他的逻辑,这里以默认的非公平锁为例来看看lock()和unlock()的原理。

先来看看RenntrantLock与AQS之间的关系:

public class ReentrantLock implements Lock, java.io.Serializable {    abstract static class Sync extends AbstractQueuedSynchronizer {        abstract void lock();        protected final boolean isHeldExclusively() {          ..         }        final ConditionObject newCondition() {            return new ConditionObject();        }            protected final boolean tryRelease(int releases) {          ..         }        final boolean nonfairTryAcquire(int acquires) {           ...         }    }    static final class NonfairSync extends Sync {        final void lock() { ... }            protected final boolean tryAcquire(int acquires) {            return nonfairTryAcquire(acquires);        }    }    static final class FairSync extends Sync {        final void lock() {            acquire(1);        }        protected final boolean tryAcquire(int acquires) {            ..         }    }}

可以看到,Sync继承自AQS,利用Sync实现了公平和非公平两种同步锁,同步队列中的结构如下:

+------+  prev +-----+       +-----+ head |      | 

加锁过程

下面聚焦到lock这个方法上。

ReentrantLockpublic void lock() {        sync.lock(); } NonfairSync final void lock()      /**        这里一进来就以cas的方式抢占同步状态        如果当前同步状态值为0,并且成功将其设置为1        则当前线程成功获取锁,lock方法返回。                这里也体现了锁的非公平性:        公平锁:严格按照锁的获取顺序来分配锁        非公平锁:不管后面有没有线程在等待锁资源,都会抢占同步状态      */      if (compareAndSetState(0, 1))          setExclusiveOwnerThread(Thread.currentThread());      else          /**           如何失败,则调用父类AQS的acquire方法          */          acquire(1); }

下面来看看AQS的acquire()方法:

public final void acquire(int arg) {     /**       这里包含了AQS的完成逻辑       1. 尝试调用子类的tryAcquire()方法来获取同步状态值          (这里的获取同步状态就是将同步状态从0设置成1)       2. 如果设置失败,调用addWaiter方法,将当前节点构造成一个Node          节点,然后用cas的方式线程安全的添加到同步队列的末尾       3. 最后调用acquireQueued方法让当前线程进入自旋状态,获取同步状态         失败的话,利用LockSupport.park()方法将当前线程挂起,等待          前驱节点线程唤醒     */     if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt(); }

我们一个方法一个方法的看,首先是子类(NonfairSync)的tryAcquire()方法:

protected final boolean tryAcquire(int acquires) {     return nonfairTryAcquire(acquires);}final boolean nonfairTryAcquire(int acquires) {      final Thread current = Thread.currentThread();      int c = getState();      //如果当前状态为0,说明锁没有被其他线程获取      if (c == 0) {          //尝试设置同步状态,获取锁          if (compareAndSetState(0, acquires)) {              setExclusiveOwnerThread(current);              return true;          }      }      /**         这里处理锁d 可重入性。         可重入性指的是同一个线程多次调用同一个需要锁的方法时,         不应该被阻塞。                  如果上次获取锁的线程是当前线程,就将同步状态+1      */      else if (current == getExclusiveOwnerThread()) {          int nextc = c + acquires;          if (nextc < 0) // overflow              throw new Error("Maximum lock count exceeded");          //这里没有用cas,是因为,如果逻辑走到这里          //当面当前线程已经获取到了锁          setState(nextc);          return true;      }      return false;}

这个方法主要是利用AQS实现了上层组件(ReentrantLock)的锁的语义。

下面再看addWaiter这个方法。

/**  走到这里,说明当先线程获取同步锁失败了*/private Node addWaiter(Node mode) {       //创建一个同步队列中的Node节点        Node node = new Node(Thread.currentThread(), mode);        // Try the fast path of enq; backup to full enq on failure        /**          这里来了一个 fast try          去将当前这个node节点加入到同步队列的末尾                    之所以这么做,是为了尽量避免进入下面enq的自旋,          如果这里设置失败了,再进入自旋状态,从这里也可以看出          Dong Lea的功力,对性能的极致要求        */        Node pred = tail;        if (pred != null) {            node.prev = pred;            if (compareAndSetTail(pred, node)) {                pred.next = node;                return node;            }        }        //前面尝试失败了,就进入自旋cas模式        enq(node);        return node;}private Node enq(final Node node) {        for (;;) {            Node t = tail;            if (t == null) { // Must initialize                if (compareAndSetHead(new Node()))                    tail = head;            } else {                node.prev = t;                //这里就是尝试将tail设置成新增的节点                if (compareAndSetTail(t, node)) {                    t.next = node;                    return t;                }            }        }}

最后,再来看看acquireQueued这个方法:

final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            /**               成功添加到同步队列以后,就进入了自选状态            */            for (;;) {                /**                  这里仍然不放弃,判断其前驱节点是否为头节点&                  能够获取同步状态,如果true,则说明获取到锁                  直接返回,跳出自旋                */                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return interrupted;                }                /**                    如果失败了,这里会做两件事                    1、将其前驱节点状态设置为SINGAL,目的是让                       前驱节点执行完成时,去通知当前节点唤醒                    2、利用LockSupport.park()挂起当前线程                                   */                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            if (failed)                cancelAcquire(node);        }}

到这里,ReentrantLock 独占非公平锁的加锁的加锁过程就完成了,同步队列的状态如下图所示:

96e1cac365c2

总结一下:

尝试获取同步状态;

如果成功,直接返回;

如果失败,构造一个Node节点,CAS+自旋将其线程安全的加入同步队列末尾;

然后每个节点代表的线程进入自旋,设置前驱节点状态为SINGAl并挂起,等待被前驱节点唤醒;

解锁过程

相比于加锁,解锁过程要简单的多,主要就是释放同步状态,下面看看unlock的逻辑:

ReentrantLockpublic void unlock() {     sync.release(1); }  AQSpublic final boolean release(int arg) {        //这里调用子类实现,释放同步状态        if (tryRelease(arg)) {            Node h = head;            if (h != null && h.waitStatus != 0)                //唤醒后面的节点                unparkSuccessor(h);            return true;        }        return false; }  ReentrantLock中非公平的实现: protected final boolean tryRelease(int releases) {            int c = getState() - releases;            if (Thread.currentThread() != getExclusiveOwnerThread())                throw new IllegalMonitorStateException();            boolean free = false;            //这里处理重入锁的多次加锁的问题            //只有当同步状态-为0时,才代表释放锁状态成功            if (c == 0) {                free = true;                setExclusiveOwnerThread(null);            }            setState(c);            return free; }

到此,基于AQS的锁的获取和释放就说完了。

关注我的简书号,分享更多的技术学习文章,如果对于学习编程有很多疑惑,没有思路,不知道如何有效率的学习,可以添加我的java

交流学习群:630473711。在群里直接问我,我就是群主,需要最新系统的学习教程也可以管我要。做了很多年开发,对于学习方式,如何提高自己的技术有一定的经验,术业有专攻,多跟有经验的人交流学习,对这个行业信息了解的多,职业发展的空间就越大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个错误信息"Access denied for user 'root'@'DESKTOP-7A94ASQ' (using password: YES)"表示在使用密码登录时,访问用户为root的权限被拒绝。这个问题通常发生在腾讯云部署的服务器上,因为默认情况下,MySQL不允许root用户进行远程登录。为了解决这个问题,你可以按照以下步骤进行操作: 1. 首先,确保你已经远程连接到了服务器上,并且有管理员权限。 2. 使用root用户登录到MySQL数据库。 3. 打开MySQL数据库中的user表,这张表记录了用户的权限信息。 4. 确认你在user表中找到了'user'为root的那一行。 5. 确保这一行中的'host'字段值是你正在尝试远程访问的主机名或IP地址。 6. 确认这一行中的'password'字段值是正确的。 7. 如果'host'字段值不正确,你可以通过修改这个字段值来允许远程访问。 8. 如果'password'字段值不正确,你可以通过修改密码来解决问题。 请注意,修改数据库的权限和密码需要谨慎操作,确保你的操作是安全的,并且遵循数据库的安全最佳实践。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [连接数据库报错:Access denied for user ‘root‘@ ‘*.*.*.*‘ (using password: YES)](https://blog.csdn.net/weixin_51948690/article/details/130681048)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [Access denied for user ‘root‘@解决方案](https://blog.csdn.net/VVVXXXLLL/article/details/112062448)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值