【并发编程】【JDK源码】AQS (AbstractQueuedSynchronizer)

J.U.C实现基础

AQS、非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),concurrent包中的基础类都是使用这种模式来实现的。而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

Java中的许多可阻塞类,例如ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch、Synchronous和FutureTask等,都是基于AQS构建的。

AQS在源码中的位置

AQS

什么是AQS

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync 。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore )

AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)。
JDK源码中描述如下:

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 {@code int} value to represent state. Subclasses
must define the protected methods that change this state, and which
define what that state means in terms of this object being acquired
or released. Given these, the other methods in this class carry
out all queuing and blocking mechanics. Subclasses can maintain
other state fields, but only the atomically updated {@code int}
value manipulated using methods {@link #getState}, {@link
#setState} and {@link #compareAndSetState} is tracked with respect
to synchronization.

Subclasses should be defined as non-public internal helper
classes that are used to implement the synchronization properties
of their enclosing class. Class
{@code AbstractQueuedSynchronizer} does not implement any
synchronization interface. Instead it defines methods such as
{@link #acquireInterruptibly} that can be invoked as
appropriate by concrete locks and related synchronizers to
implement their public methods.

This class supports either or both a default exclusive
mode and a shared mode. When acquired in exclusive mode,
attempted acquires by other threads cannot succeed. Shared mode
acquires by multiple threads may (but need not) succeed. This class
does not "understand" these differences except in the
mechanical sense that when a shared mode acquire succeeds, the next
waiting thread (if one exists) must also determine whether it can
acquire as well. Threads waiting in the different modes share the
same FIFO queue. Usually, implementation subclasses support only
one of these modes, but both can come into play for example in a
{@link ReadWriteLock}. Subclasses that support only exclusive or
only shared modes need not define the methods supporting the unused mode.

This class defines a nested {@link ConditionObject} class that
can be used as a {@link Condition} implementation by subclasses
supporting exclusive mode for which method {@link
#isHeldExclusively} reports whether synchronization is exclusively
held with respect to the current thread, method {@link #release}
invoked with the current {@link #getState} value fully releases
this object, and {@link #acquire}, given this saved state value,
eventually restores this object to its previous acquired state. No
{@code AbstractQueuedSynchronizer} method otherwise creates such a
condition, so if this constraint cannot be met, do not use it. The
behavior of {@link ConditionObject} depends of course on the
semantics of its synchronizer implementation.

This class provides inspection, instrumentation, and monitoring
methods for the internal queue, as well as similar methods for
condition objects. These can be exported as desired into classes
using an {@code AbstractQueuedSynchronizer} for their
synchronization mechanics.

Serialization of this class stores only the underlying atomic
integer maintaining state, so deserialized objects have empty
thread queues. Typical subclasses requiring serializability will
define a {@code readObject} method that restores this to a known
initial state upon deserialization.

Usage

To use this class as the basis of a synchronizer, redefine the
following methods, as applicable, by inspecting and/or modifying
the synchronization state using {@link #getState}, {@link
#setState} and/or {@link #compareAndSetState}:


{@link #tryAcquire}
{@link #tryRelease}
{@link #tryAcquireShared}
{@link #tryReleaseShared}
{@link #isHeldExclusively}
Each of these methods by default throws {@link
UnsupportedOperationException}. Implementations of these methods
must be internally thread-safe, and should in general be short and
not block. Defining these methods is the only supported
means of using this class. All other methods are declared
{@code final} because they cannot be independently varied.

You may also find the inherited methods from {@link
AbstractOwnableSynchronizer} useful to keep track of the thread
owning an exclusive synchronizer. You are encouraged to use them
-- this enables monitoring and diagnostic tools to assist users in
determining which threads hold locks.

Even though this class is based on an internal FIFO queue, it
does not automatically enforce FIFO acquisition policies. The core
of exclusive synchronization takes the form:



Acquire:

while (!tryAcquire(arg)) {

enqueue thread if it is not already queued;

possibly block current thread;

}

Release:

if (tryRelease(arg))

unblock the first queued thread;


(Shared mode is similar but may involve cascading signals.)

Because checks in acquire are invoked before
enqueuing, a newly acquiring thread may barge ahead of
others that are blocked and queued. However, you can, if desired,
define {@code tryAcquire} and/or {@code tryAcquireShared} to
disable barging by internally invoking one or more of the inspection
methods, thereby providing a fair FIFO acquisition order.
In particular, most fair synchronizers can define {@code tryAcquire}
to return {@code false} if {@link #hasQueuedPredecessors} (a method
specifically designed to be used by fair synchronizers) returns
{@code true}. Other variations are possible.

Throughput and scalability are generally highest for the
default barging (also known as greedy,
renouncement, and convoy-avoidance) strategy.
While this is not guaranteed to be fair or starvation-free, earlier
queued threads are allowed to recontend before later queued
threads, and each recontention has an unbiased chance to succeed
against incoming threads. Also, while acquires do not
"spin" in the usual sense, they may perform multiple
invocations of {@code tryAcquire} interspersed with other
computations before blocking. This gives most of the benefits of
spins when exclusive synchronization is only briefly held, without
most of the liabilities when it is not. If so desired, you can
augment this by preceding calls to acquire methods with
"fast-path" checks, possibly prechecking {@link #hasContended}
and/or {@link #hasQueuedThreads} to only do so if the synchronizer
is likely not to be contended.

This class provides an efficient and scalable basis for
synchronization in part by specializing its range of use to
synchronizers that can rely on {@code int} state, acquire, and
release parameters, and an internal FIFO wait queue. When this does
not suffice, you can build synchronizers from a lower level using
{@link java.util.concurrent.atomic atomic} classes, your own custom
{@link java.util.Queue} classes, and {@link LockSupport} blocking
support.

Usage Examples

Here is a non-reentrant mutual exclusion lock class that uses
the value zero to represent the unlocked state, and one to
represent the locked state. While a non-reentrant lock
does not strictly require recording of the current owner
thread, this class does so anyway to make usage easier to monitor.
It also supports conditions and exposes
one of the instrumentation methods:

 {@code

class Mutex implements Lock, java.io.Serializable {

// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
  // Reports whether in locked state
  protected boolean isHeldExclusively() {
    return getState() == 1;
  }

  // Acquires the lock if state is zero
  public boolean tryAcquire(int acquires) {
    assert acquires == 1; // Otherwise unused
    if (compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(Thread.currentThread());
      return true;
    }
    return false;
  }

  // Releases the lock by setting state to zero
  protected boolean tryRelease(int releases) {
    assert releases == 1; // Otherwise unused
    if (getState() == 0) throw new IllegalMonitorStateException();
    setExclusiveOwnerThread(null);
    setState(0);
    return true;
  }

  // Provides a Condition
  Condition newCondition() { return new ConditionObject(); }

  // Deserializes properly
  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
  }
}

// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();

public void lock()                { sync.acquire(1); }
public boolean tryLock()          { return sync.tryAcquire(1); }
public void unlock()              { sync.release(1); }
public Condition newCondition()   { return sync.newCondition(); }
public boolean isLocked()         { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
  sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
    throws InterruptedException {
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

}}

Here is a latch class that is like a
{@link java.util.concurrent.CountDownLatch CountDownLatch}
except that it only requires a single {@code signal} to
fire. Because a latch is non-exclusive, it uses the {@code shared}
acquire and release methods.

 {@code

class BooleanLatch {

private static class Sync extends AbstractQueuedSynchronizer {
  boolean isSignalled() { return getState() != 0; }

  protected int tryAcquireShared(int ignore) {
    return isSignalled() ? 1 : -1;
  }

  protected boolean tryReleaseShared(int ignore) {
    setState(1);
    return true;
  }
}

private final Sync sync = new Sync();
public boolean isSignalled() { return sync.isSignalled(); }
public void signal()         { sync.releaseShared(1); }
public void await() throws InterruptedException {
  sync.acquireSharedInterruptibly(1);
}

}}

@since 1.5
@author Doug Lea

如JDK的文档中所说,使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getState,setState,compareAndSetState这几个方法来设置获取状态

boolean tryAcquire(int arg)
boolean tryRelease(int arg)
int tryAcquireShared(int arg)
boolean tryReleaseShared(int arg)
boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquire、 tryRelease、isHeldExclusively而支持共享获取的同步器应该实现tryAcquireShared、tryReleaseShared、isHeldExclusively。下面以 CountDownLatch 举例说明基于AQS实现同步器, CountDownLatch 用同步状态持有当前计数,countDown方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await调用acquire,如果计数器为0,acquire 会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:

public class CountDownLatch {
    /**
     * 基于AQS的内部Sync
     * 使用AQS的state来表示计数count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        Sync(int count) {
            // 使用AQS的getState()方法设置状态
            setState(count);
        }
        int getCount() {
            // 使用AQS的getState()方法获取状态
            return getState();
        }
        // 覆盖在共享模式下尝试获取锁
        protected int tryAcquireShared(int acquires) {
            // 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
            return (getState() == 0) ? 1 : -1;
        }
        // 覆盖在共享模式下尝试释放锁
        protected boolean tryReleaseShared(int releases) {
            // 在for循环中Decrement count直至成功;
            // 当状态值即count为0的时候,返回false表示 signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
    private final Sync sync;
    // 使用给定计数值构造CountDownLatch
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    // 让当前线程阻塞直到计数count变为0,或者线程被中断
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回true
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    // count递减
    public void countDown() {
        sync.releaseShared(1);
    }
    // 获取当前count值
    public long getCount() {
        return sync.getCount();
    }
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

参考资料:
JAVA并发编程: CAS和AQS
《JAVA并发编程实战》

猜你喜欢

转载自www.cnblogs.com/z00377750/p/9227881.html