AQS源码分析之独占锁和共享锁

本文深入分析了AQS(AbstractQueuedSynchronizer)的源码,探讨了其如何实现独占锁和共享锁。独占锁如ReentrantLock,遵循公平或非公平策略,通过AQS的state变量和FIFO队列管理线程竞争。共享锁如CountDownLatch,允许多个线程共享资源,等待计数器归零后释放。AQS通过维护状态和队列,实现线程的等待、唤醒和同步操作。
摘要由CSDN通过智能技术生成

简介

AQS实现锁机制并不是通过synchronized——给对象加锁实现的,事实上它仅仅是一个工具类!它没有使用更高级的机器指令,也不靠关键字,更不依靠JDK编译时的特殊处理,仅仅作为一个普普通通的类就完成了代码块的访问控制。

AQS使用标记位+队列的方式,记录获取锁、竞争锁、释放锁等一些类锁操作。但更准确的说,AQS并不关心什么是锁,对于AQS来说它只是实现了一系列的用于判断资源是否可以访问的API,并且封装了在访问资源受限时,将请求访问的线程加入队列、挂起、唤醒等操作。AQS关心的问题如下:

  1. 资源不可访问时,怎么处理?
  2. 资源时可以被同时访问,还是在同一时间只能被一个线程访问?
  3. 如果有线程等不及资源了,怎么从AQS队列中退出?

至于资源能否被访问的问题,则交给子类去实现。

站在使用者的角度,AQS的功能主要分为两类:独占锁共享锁。在它的所有子类中,要么实现了它的独占功能的API,要么实现了共享功能的API,但不会同时使用两套API,即使是ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别使用两套API来实现的。

  • 当AQS的子类实现独占功能时,如ReentrantLock,资源是否可以被访问被定义为:只要AQS的state变量不为0,并且持有锁的线程不是当前线程,那么代表资源不可访问。
  • 当AQS的子类实现共享功能时,如CountDownLatch,资源是否可以被访问被定义为:只要AQS的state变量不为0,那么代表资源不可以为访问。

AQS类继承结构图

image

独占锁

ReentrantLock是AQS独占功能的一个实现,通常的使用方式如下:

reentrantLock.lock();
// do something
reentrantLock.unlock();

ReentrantLock会保证执行do something在同一时间有且只有一个线程获取到锁,其余线程全部挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒重新开始竞争锁。

ReentrantLock的加锁全部委托给内部代理类完成,ReentrantLock只是封装了统一的一套API而已,而ReentrantLock又分为公平锁非公平锁

abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
  • 公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序,并依此顺序获得锁,类似于排队吃饭;
  • 非公平锁:每个线程抢占锁的顺序不变,谁运气好,谁就获得锁,和调用lock方法的先后顺序无关,类似后插入。

换句话说,公平锁和非公平锁的唯一的区别是在获取锁的时候是直接去获取锁,还是进入队列排队的问题。

获取独占锁的流程

image

FairSync的tryAcquire()方法分析

   protected final boolean tryAcquire(int acquires) {
       final Thread current = Thread.currentThread();/*获取当前线程*/
       int c = getState(); /*获取父类AQS中的标志位*/
       if (c == 0) {
           if (!hasQueuedPredecessors()
                   /*说明队列中没有其他线程:没有线程正在占有锁,那么修改一下状态位*/
                   //注意:这里的acquires是在lock的时候传递来的,从上面的图中可以知道,这个值是写死的1
                   && compareAndSetState(0, acquires)) {
               // 如果通过CAS操作将状态为更新成功则代表当前线程获取锁,
               // 因此,将当前线程设置到AQS的一个变量中,说明这个线程拿走了锁。
               setExclusiveOwnerThread(current);
               return true;/*返回true,说明已拿到锁*/
           }
       } else if (current == getExclusiveOwnerThread()) {
           /*如果当前state不为0,说明锁已经被拿走,那么判断是否是当前线程拿走了锁*/
           /*因为锁是可重入的,可以重复lock,unlock*/
           int nextc = c + acquires;
           if (nextc < 0)
               throw new Error("Maximum lock count exceeded");
           setState(nextc);
           return true;
       }
       return false;
   }

如果当前线程获取到锁,tryAcquire()返回true,否则返回false,这时会返回到AQS的acquire()方法。

如果没有获取到锁,那么应该将当前线程放到队列中去,只不过,在放之前,需要做些包装。

AQS的addWaiter()方法分析

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        /*将节点加入到队列尾部*/
        node.prev = pred;
        /*尝试使用CAS方式修改尾节点,但是在【并发情况】下,可能修改失败*
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值