AbstractQueuedSynchronizer简介

本文深入探讨了AQS(AbstractQueuedSynchronizer)在Java并发中的作用,它是一个用于实现阻塞锁和其他同步器的基础框架。AQS通过FIFO等待队列管理锁的获取和释放,简化了锁的实现,如ReentrantLock。文章通过ReentrantLock的源码分析,展示了AQS如何处理锁的获取、释放、等待和唤醒机制,揭示了其核心流程和工作原理。
摘要由CSDN通过智能技术生成

AbstractQueuedSynchronizer(后面我们使用AQS做简称)是什么?

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues.This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state.

上面的JDK注释中对AQS的介绍,AQS是一个基于先进先出等待队列来实现阻塞锁和相关同步器的框架。这个类是那些使用整数来代表状态的同步器的有用的基。(个人翻译,可能不是很准确)。这里说是框架,其实就一个类。

那到底在什么情况下会使用到AQS呢?

在日常的编程中我们经常会碰到并发的情况。有并发,就有资源共享;有资源共享就需要处理资源同步访问。处理同步的时候,就要处理竞争发生时候的等待问题以及竞争解除后的唤起的问题。AQS就是一个便于我们实现这种同步机制的框架。我们日常中使用到的ReentrantLock、ReentrantReadWriteLock以及ArrayBlockingQueue等都是基于AQS实现的。

如果没有AQS的话,会怎么样?

我们可以设想一下,在没有AQS的情况下,我们要实现锁需要怎么做呢?

  1. 锁的状态是如何记录的,如何表示这个锁被持有的情况
  2. 如何获取锁
  3. 锁获取不到如何处理,直接返回,还是等待锁被释放。
  4. 如何释放锁
  5. 锁释放后,如何唤起哪些在等这个锁的线程。
  6. 共享锁、排他锁、公平锁、非公平锁,这些锁的特定如何处理。

上面这些问题,都是我们在实现锁的时候都要考虑的事情

在AQS的情况下,又会是怎么样的呢?

AQS作为基础类,主要解决了在锁不能获取的情况下的等待,以及锁释放后的唤起。锁状态的定义,如何获取锁以及如何释放锁,都是需要相应的同步机制自己实现的。所以在使用AQS的时候,需要实现下面几个方法:

  • tryAcquire(),获取排他锁
  • tryRelease(),释放排他锁
  • tryAcquireShared(),获取共享锁
  • tryReleaseShared(),释放共享锁
  • isHeldExclusively(),是不是持有排他锁

类似ReentrantLock,使用state来标识锁的状态,state = 0表示锁没有被获取,当state > 0表示锁已经被获取了。另外实现了AQS的tryAcquire()和tryRelease()来处理state的状态,来处理锁的状态。这样就可以实现了最基础了ReentrantLock了。所以在AQS的加持下,实现类似的阻塞锁非常的方便。

上面我们介绍了AQS是什么,以及AQS具体的使用场景,下面我们详细介绍下AQS具体的实现机制。

我们先简单看下AQS的核心流程。

下面我们结合ReentrantLock来详细看下基于AQS如果实现排他锁的。

首先我们来看下ReentrantLock是如何用int类型的state来表示锁的状态的。state = 0表示锁没有被获取。state > 0表示锁已经被持有了。因为是可重入的锁,state表示了这个锁被同一个线程获取了多少次。

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
         ......
    }

    public boolean lock() {
        return sync.lock(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

 上面的代码片段是ReentrantLock核心代码,为了方便解释,把其他的代码都省略掉了。首先我们可以看到,在使用AQS的时候,一般不是直接实现AQS,而且创建一个内部的辅助类。 

Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class

然后加锁和释放锁对应的lock和unlock,直接调用辅助类的lock和unlock。

下面我们看下Sync类的代码。

abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();  //获取当前的线程
            int c = getState();  //获取当前的状态 0表示没有人使用这个锁
            if (c == 0) {   
                if (compareAndSetState(0, acquires)) { //通过cas的方式进行锁的获取
                    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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

  • lock方法被当做一个抽象方法,具体的公平锁和非公平锁会有对应的实现。
  • nonfairTryAcquire ,是一个非公平锁获取锁的方式,通过状态来判断锁是不是被持有,如果没有被持有则通过cas方法来获取锁,成功后设置持有锁的进程;如果已经被持有,则检查是不是被当前线程持有。
  • tryRelease,释放锁,更新状态,如果已经全都释放了,则更新持有锁的进程。因为是排他锁,在设置state的时候,并没有使用cas的方案。
  • isHeldExclusively 表示是不是被当前线程占有了。

上面我们来看下非公平锁NonfairSync的实现。

static final class NonfairSync extends Sync {
        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        @ReservedStackAccess
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
  • lock,获取锁,如果当天状态是0,表示没有线程持有这个锁,则尝试获取锁,获取成功后直接当前持有的线程,否则调用AQS中的acquire进行锁的竞争。对于公平锁来说,还要去检查当前是不是还有线程在等待队列中,如果有的话,则会acquire排到队列后面。
  • tryAcquire,尝试获取锁,调用了上面说过的nonfairTryAcquire方法,这个方法是会被AQS调用的。

下面我们来看下AQS中的acquire方法。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

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);
        }
    }
  • tryAcquire就是子类实现,尝试获取锁。如果获取锁成功,则直接返回了。如果获取失败,通过addWaiter加入到队列,并且通过acquireQueued进行挂起等待。
  • acquireQueued,首先尝试获取锁,失败的话,则通过parkAndCheckInterrupt进行挂起。

上面看了获取锁的方法acquire,下面看下释放锁的release。

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  • 释放锁的就比较简单,首先调用子类实现的tryRelease,尝试释放锁。如果成功,唤起上面通过parkAndCheckInterrupt被挂起的线程。唤起后会重新尝试唤起锁,获取失败会被重新挂起。

总结以下,AQS通过tryAcquire以及tryRelease两个方法,来进行锁的获取以及释放,两个都是非阻塞的方法。如果获取成功则返回,如果获取失败,则添加队列。在队列中获取锁,获取失败则挂起,等待锁被释放后被重新唤起。唤起后还是会去尝试获取锁。释放锁的时候如果检查到已经全部释放,则会唤起被挂起的线程。这样通过tryAcquire和tryRelease,实现了锁的获取以及等待,以及锁的释放。具体锁状态的控制,子类则是通过tryAcquire和tryRelease进行控制的。

上面在介绍ReentrantLock的时候,把很多代码给简化了。大家可以了解基本的原理后,再去读源码会事半功倍。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值