AQS學習筆記(一)- AbstractQueuedSynchronizer的文檔翻譯【從零開始】

寫在前面的話

AQS(AbstractQueuedSynchronizer)隊列同步器,是JUC中非常重要的一個組件,基於它可以簡單高效地構建一些通用的鎖和同步器,如ReentrantLock、Semaphore等(本文學習內容基於JDK1.8),本文主要關注AQS的源碼實現及基於AQS實現的一些常用的同步組件

網絡中對AQS的學習已經不乏有非常優秀的總結和筆記,比如摘自以上一段摘自AQS原理學習筆記;我看過《Java併發編程的藝術》,也有做架構的朋友告訴過我AQS寫的非常好;作者Doug Lea是JSR-166專家組成員之一,也是公認的併發編程大師,寫出來的都值得學習;
總之就是非常牛批的意思,但整個JUC畢竟是這麼大一個框架,從零開始的話,小白可能也和我一樣不知道從何入手,其實總之想深入學都是要看的,我就挑着開始做自己的筆記。由於工作關係也不確定能定期更新,但也要儘自己努力吧

開頭就從AbstractQueuedSynchronizer類文檔的翻譯開始,本人英語水平能力有限,有任何問題都懇請指正

正文

類(AbstractQueuedSynchronizer)文檔

旨在提供一個實現阻塞鎖和相關依賴於FIFO隊列的同步器(semaphores信號量, events事件 等等)的框架。這個類是作爲大多數同步器的基礎而設計的,方式是用一個原子的int值標識狀態,狀態就是指能夠表示實現AQS的對象是正在被獲取或者釋放,其子類必須實現這些改變狀態的protected方法;相對的,AQS類中的其他方法都是在實現隊列或者阻塞的機制。 子類可以維護自己的屬性,但如果想原子地修改同步狀態,可以使用同步器提供的 getState方法、setState方法以及compareAndSetState方法

AQS的子類應該被定義爲非公開的內部類,作爲在封閉類中實現同步屬性。 AbstractQueuedSynchronizer類中並沒有實現任何的同步接口,反而定義了像acquireInterruptibly這樣的方法能被適當的用在Lock的實現或者相關的同步器裏的public方法裏。

該類支持一種默認的獨佔模式或是共享模式,或者兩者同時支持。 當在獨佔模式裏獲取狀態時,僅有一次嘗試能獲取而其他線程不會成功;共享模式裏如果多線程嘗試獲取狀態值會有可能成功(但不應該成功)。該類本身並不“理解”,除了字面本身的差異,當在共享模式裏一個線程獲取成功時,下一個等待線程(如果存在)還必須確定它是不是也能獲取成功。 線程即便在不同的模式裏等待,但也會共享使用相同的FIFO隊列。 通常,子類的實現一般只使用一種模式,但是確實也能夠兩種模式一同使用,例如在ReadWriteLock中就是這樣。子類只使用一種模式時,就不需要去定義另一種模式的方法了。

該類定義了一個嵌套的成員內部類ConditionObject,該類可以被用作Condition接口的實現類來支持獨佔模式,獨佔模式中有一個isHeldExclusively方法,可以得知當前線程是否排他得持有同步狀態,release方法配合着getState得知當前同步值,可以完全使對象釋放同步狀態;acquire方法在已得知已寫入的狀態值的時候,最終將使用AQS的對象還原到先前獲取的狀態。 AbstractQueuedSynchronizer沒有其他方法創建這樣的condition對象,如果真有了,約束將不會其作用,所以不要這麼做。 ConditionObject的行爲當然完全取決於同步器本身實現的語義(即鎖的語義)。

該類提供了對內部FIFO隊列和condition對象的檢查、監聽、維護方法,這些方法可以根據需要被導出使用,來維護AbstractQueuedSynchronizer同步器同步機制,(相同於同步器對同步狀態的管理實現依賴於底層內部的隊列和condition對象)

這個類的序列化僅存儲原子Integer類來維護同步狀態,所以在反序列化的時候,得到的對象內部的線程同步隊列是空的。 典型的子類如果需要有序列化的能力,需要定義readObject方法,目的是使反序列化的對象恢復到初始化的狀態。

用法

爲了作爲同步器的基礎而去使用AQS這個類,需要重新定義下列方法;如果需要檢查或修改同步狀態值可使用的方法爲 getState方法、setState方法以及compareAndSetState方法。

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

每一個方法默認都會拋出UnsupportedOperationException異常,每一個方法實現都必須是內在線程安全的,一般來說也應該是比較短而且不阻塞的。定義這些方法也僅在使用這個類的類中使用,其他的所有的方法都被聲明成final

你也可能會發現一些從AbstractOwnableSynchronizer類裏繼承過來的方法用來跟蹤擁有排他同步器的線程是挺方便的,我也鼓勵你去使用它們,這些方法可以用來當做監聽或診斷工具去協助開發者判斷是哪些線程持有鎖。

即便這個類是基於內部的FIFO隊列構建,但它不會自動執行FIFO獲取策略。 獨佔同步器的核心採取以下這樣的形式

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;

(共享模式相似但會涉及級聯信號)

因爲在調用獲取的過程中,檢查校驗是先於入列的,所以一個新的獲取中的線程可能會插隊到隊列中已經入列或者阻塞的線程的前面。 如果你需要的話,可以定義tryAcquire或者tryAcquireShared通過內部調用檢查的方法來排除衝突,因此提供一個公平的FIFO隊列獲取順序。特別地,大多數公平的同步器可以定義tryAcquire,當hasQueuedPredecessors方法(一個專門設計來爲公平同步器使用的方法,用來檢查是否還有線程比當前線程等待的更久) 返回true的時候返回false。 其他的變化是可能發生的。

使用默認的衝突策略,吞吐量和可擴展性都是最高的,該策略也稱爲greedy策略,renouncement策略或者convoy-avoidance策略。 但此策略並不能保證公平或starvation-free,更早時候入列的線程被允許可以和新加入的線程重競爭,每一次和入列的新線程重競爭時都有同樣的機會勝出。此外,雖然獲取同步值不是通常意義上’自旋’的,但獲取過程中可能會在阻塞之前多次調用tryAcquire方法,這些方法都散佈在其他的計算中。 當發生短暫的持有獨佔同步時,自旋帶來的收益會最大化,當長時間時自旋也不是主要的責任承擔者。 所以有需要的話,可以通過之前調用帶有“快速路徑”檢查的獲取同步值的方法來擴展,可能的方法是預先檢查hasContended方法或者是hasQueuedThreads方法,以僅當同步器不可能被競爭時再自旋獲取同步值

此類爲實現同步提供了一個高效且易擴展的基礎,部分是因爲專門界定了同步器的使用範圍,該同步器依賴於一個intstate,可以獲取和釋放;部分是因爲內部維護了一個FIFO等待隊列;如果這些還不足以滿足需求,你能從更底層使用java.util.concurrent.atomic 原子包下的原子類、 自定義的隊列java.util.Queue 以及LockSupport類提供阻塞支持來構建自定義的同步器。

樣例

這裏有一個不可重入的、相互的排它鎖的類,使用數字0來表示未加鎖狀態,數字1表示加鎖狀態。 雖然一個不可重入鎖不會嚴格需要記錄當前持有鎖的線程,這個類這麼做了只是因爲讓它更容易去監控。 它還支持Condition,也暴露了一些instrumentation方法

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public 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));
    }
}

(筆者: 這個類雖然簡單但也是基礎,更是一次Lock和AQS的理解的敲門磚,建議多寫幾次,以後還會在後續的學習中用到)

這裏是一個Latch類(門閂),除了該類僅需要單個signal都類似於java.util.concurrent.CountDownLatch CountDownLatch類。 因爲一個latch類是非獨佔的,所以它使用了共享的acquire和release方法

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

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();   
    }
}

@since 1.5
@author Doug Lea

下一篇:AQS學習筆記(二)- AbstractQueuedSynchronizer概覽

(後續還會陸續更新內部類和重要方法的文檔)

Node內部類

該內部類就是用來定義等待隊列的,非常重要

附原文(JDK1.8)

/**
 * 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.
 *
 * <p>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.
 *
 * <p>This class supports either or both a default <em>exclusive</em>
 * mode and a <em>shared</em> 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 &quot;understand&quot; 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.
 *
 * <p>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.
 *
 * <p>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.
 *
 * <p>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.
 *
 * <h3>Usage</h3>
 *
 * <p>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}:
 *
 * <ul>
 * <li> {@link #tryAcquire}
 * <li> {@link #tryRelease}
 * <li> {@link #tryAcquireShared}
 * <li> {@link #tryReleaseShared}
 * <li> {@link #isHeldExclusively}
 * </ul>
 *
 * 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 <em>only</em> supported
 * means of using this class. All other methods are declared
 * {@code final} because they cannot be independently varied.
 *
 * <p>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.
 *
 * <p>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:
 *
 * <pre>
 * Acquire:
 *     while (!tryAcquire(arg)) {
 *        <em>enqueue thread if it is not already queued</em>;
 *        <em>possibly block current thread</em>;
 *     }
 *
 * Release:
 *     if (tryRelease(arg))
 *        <em>unblock the first queued thread</em>;
 * </pre>
 *
 * (Shared mode is similar but may involve cascading signals.)
 *
 * <p id="barging">Because checks in acquire are invoked before
 * enqueuing, a newly acquiring thread may <em>barge</em> 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 <em>fair</em> 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.
 *
 * <p>Throughput and scalability are generally highest for the
 * default barging (also known as <em>greedy</em>,
 * <em>renouncement</em>, and <em>convoy-avoidance</em>) 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
 * &quot;spin&quot; 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 isn't. 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.
 *
 * <p>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.
 *
 * <h3>Usage Examples</h3>
 *
 * <p>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:
 *
 *  <pre> {@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));
 *   }
 * }}</pre>
 *
 * <p>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.
 *
 *  <pre> {@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);
 *   }
 * }}</pre>
 *
 * @since 1.5
 * @author Doug Lea
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章