StampedLock

1.官方文檔

A capability-based lock with three modes for controlling 
read/write access. The state of a StampedLock consists of a 
version and mode. Lock acquisition methods return a stamp 
that represents and controls access with respect to a lock state; 
"try" versions of these methods may instead return the special 
value zero to represent failure to acquire access. Lock release 
and conversion methods require stamps as arguments, and fail 
if they do not match the state of the lock. The three modes are:

* Writing. Method writeLock() possibly blocks waiting for 
  exclusive access, returning a stamp that can be used in method 
  unlockWrite(long) to release the lock. Untimed and timed 
  versions of tryWriteLock are also provided. When the lock is 
  held in write mode, no read locks may be obtained, and all 
  optimistic read validations will fail.

* Reading. Method readLock() possibly blocks waiting for non-
  exclusive access, returning a stamp that can be used in method 
  unlockRead(long) to release the lock. Untimed and timed 
  versions of tryReadLock are also provided.

* Optimistic Reading. Method tryOptimisticRead() returns a 
  non-zero stamp only if the lock is not currently held in write 
  mode. Method validate(long) returns true if the lock has not 
  been acquired in write mode since obtaining a given stamp. 
  This mode can be thought of as an extremely weak version of a 
  read-lock, that can be broken by a writer at any time. The use 
  of optimistic mode for short read-only code segments often 
  reduces contention and improves throughput. However, its use 
  is inherently fragile. Optimistic read sections should only read 
  fields and hold them in local variables for later use after 
  validation. Fields read while in optimistic mode may be wildly 
  inconsistent, so usage applies only when you are familiar 
  enough with data representations to check consistency and/or 
  repeatedly invoke method validate(). For example, such steps 
  are typically required when first reading an object or array 
  reference, and then accessing one of its fields, elements or 
  methods.

This class also supports methods that conditionally provide 
conversions across the three modes. For example, method 
tryConvertToWriteLock(long) attempts to "upgrade" a mode, 
returning a valid write stamp if (1) already in writing mode (2) in 
reading mode and there are no other readers or (3) in optimistic 
mode and the lock is available. The forms of these methods are 
designed to help reduce some of the code bloat that otherwise 
occurs in retry-based designs.

StampedLocks are designed for use as internal utilities in the 
development of thread-safe components. Their use relies on 
knowledge of the internal properties of the data, objects, and 
methods they are protecting. They are not reentrant, so locked 
bodies should not call other unknown methods that may try to 
re-acquire locks (although you may pass a stamp to other 
methods that can use or convert it). The use of read lock 
modes relies on the associated code sections being side-effect-
free. Unvalidated optimistic read sections cannot call methods 
that are not known to tolerate potential inconsistencies. Stamps 
use finite representations, and are not cryptographically secure 
(i.e., a valid stamp may be guessable). Stamp values may 
recycle after (no sooner than) one year of continuous operation. 
A stamp held without use or validation for longer than this 
period may fail to validate correctly. StampedLocks are 
serializable, but always deserialize into initial unlocked state, so 
they are not useful for remote locking.

The scheduling policy of StampedLock does not consistently 
prefer readers over writers or vice versa. All "try" methods are 
best-effort and do not necessarily conform to any scheduling or 
fairness policy. A zero return from any "try" method for 
acquiring or converting locks does not carry any information 
about the state of the lock; a subsequent invocation may 
succeed.

Because it supports coordinated usage across multiple lock 
modes, this class does not directly implement the Lock or 
ReadWriteLock interfaces. However, a StampedLock may be 
viewed asReadLock(), asWriteLock(), or asReadWriteLock() in 
applications requiring only the associated set of functionality.

Sample Usage. The following illustrates some usage idioms in 
a class that maintains simple two-dimensional points. The 
sample code illustrates some try/catch conventions even 
though they are not strictly needed here because no exceptions 
can occur in their bodies.

基於功能的鎖,具有三種控制讀/寫訪問的模式。 StampedLock的狀態包括版本和模式。鎖獲取方法返回一個表示和控制鎖狀態訪問的stamp;這些方法的“try”版本可能會返回特殊值零以表示獲取訪問失敗。鎖釋放和轉換方法需要stamp作爲參數,如果它們與鎖狀態不匹配則會失敗。這三種模式是:

  • 寫。方法writeLock()可能阻塞等待獨佔訪問,返回可在方法unlockWrite(long)中使用的stamp以釋放鎖。還提供了不定時和定時版本的tryWriteLock。當鎖保持在寫模式時,不能獲得讀鎖,並且所有樂觀讀驗證都將失敗。
  • 讀。方法readLock()可能阻塞等待非獨佔訪問,返回可以在方法unlockRead(long)中使用的stamp以釋放鎖。還提供了不定時和定時版本的tryReadLock。
  • 樂觀讀。方法tryOptimisticRead()僅在當前鎖未處於寫模式時才返回非零stamp。方法validate(long)如果在獲取給定stamp後沒有被寫模式獲取鎖,則返回true。這種模式可以被認爲是讀鎖的極弱版本,可以隨時由寫線程打破。對短的只讀代碼段使用樂觀模式通常可以減少爭用並提高吞吐量。但是,它的使用本質上是脆弱的。樂觀讀取部分應該只讀取字段並在驗證後將它們保存在局部變量中,以便以後使用。在樂觀模式下讀取的字段可能非常不一致,因此僅當您熟悉數據表示以檢查一致性和/或重複調用方法validate()時才能使用。例如,在首先讀取對象或數組引用,然後訪問其字段、元素或方法之一時,通常需要這些步驟。

此類還支持提供有條件地跨三種模式的轉換的方法。例如,方法tryConvertToWriteLock(long)嘗試“升級”一個模式並返回一個有效的寫stamp,如果

  • 1)已經處於寫模式
  • 2)處於讀模式並且沒有其他讀線程
  • 3)處於樂觀模式並且鎖是可用的。

這些方法的形式旨在幫助減少基於重試設計中出現的一些代碼膨脹。

StampedLocks設計用作開發線程安全組件的內部實用程序。它們的使用依賴於對它們所保護的數據、對象和方法的內部屬性的瞭解。它們不是可重入的,因此鎖定的主體不應該調用可能嘗試重新獲取鎖的其他未知方法(儘管您可以將stamp傳遞給可以使用或轉換它的其他方法)。讀模式的使用依賴於相關的代碼部分是無副作用的。未經驗證的樂觀讀取部分無法調用未知容忍潛在不一致的方法。stamps使用有限表示,並且不是加密安全的(即可猜測有效stamp)。stamp值可在連續操作一年後(不快於一年)後重循環。未經使用或驗證超過此期限而持有的stamp可能無法正確驗證。 StampedLocks是可序列化的,但總是反序列化爲初始無鎖狀態,因此它們對遠程鎖定沒有用。

StampedLock的調度策略並不總是傾向於讀線程,反之亦然。所有try方法都是盡力而爲,並不一定符合任何調度或公平政策。從任何“try”方法獲取或轉換鎖時返回零 不會攜帶有關鎖狀態的任何信息;後續調用可能會成功。

因爲它支持跨多種鎖模式的協同使用,所以此類不直接實現Lock或ReadWriteLock接口。但是,StampedLock可以在僅需要相關功能集的應用程序中被當做asRe​​adLock(),asWriteLock()或asReadWriteLock()。

示例用法。以下說明了維護簡單二維點的類中的一些用法習慣。示例代碼說明了一些try / catch約定,儘管這裏沒有嚴格要求,因爲它們的主體中不會發生異常。

 class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();

   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }

   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead();
     double currentX = x, currentY = y;
     if (!sl.validate(stamp)) {
        stamp = sl.readLock();
        try {
          currentX = x;
          currentY = y;
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }

   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) {
         long ws = sl.tryConvertToWriteLock(stamp);
         if (ws != 0L) {
           stamp = ws;
           x = newX;
           y = newY;
           break;
         }
         else {
           sl.unlockRead(stamp);
           stamp = sl.writeLock();
         }
       }
     } finally {
       sl.unlock(stamp);
     }
   }
 }

2.實現說明

The design employs elements of Sequence locks (as used in 
linux kernels; see Lameter's 
http://www.lameter.com/gelato2005.pdf and elsewhere; see 
Boehm's http://www.hpl.hp.com/techreports/2012/HPL-2012-
68.html) and Ordered RW locks (see Shirako et al 
http://dl.acm.org/citation.cfm?id=2312015) 

 Conceptually, the primary state of the lock includes a 
sequence number that is odd when write-locked and even 
otherwise. However, this is offset by a reader count that is non-
zero when read-locked.  The read count is ignored when 
validating "optimistic" seqlock-reader-style stamps.  Because 
we must use a small finite number of bits (currently 7) for 
readers, a supplementary reader overflow word is used when 
the number of readers exceeds the count field. We do this by 
treating the max reader count value (RBITS) as a spinlock 
protecting overflow updates. 

 Waiters use a modified form of CLH lock used in 
AbstractQueuedSynchronizer (see its internal documentation 
for a fuller account), where each node is tagged (field mode) as 
either a reader or writer. Sets of waiting readers are grouped 
(linked) under a common node (field cowait) so act as a single 
node with respect to most CLH mechanics.  By virtue of the 
queue structure, wait nodes need not actually carry sequence 
numbers; we know each is greater than its predecessor.  This 
simplifies the scheduling policy to a mainly-FIFO scheme that 
incorporates elements of Phase-Fair locks (see Brandenburg & 
Anderson, especially http://www.cs.unc.edu/~bbb/diss/).  In 
particular, we use the phase-fair anti-barging rule: If an 
incoming reader arrives while read lock is held but there is a 
queued writer, this incoming reader is queued.  (This rule is 
responsible for some of the complexity of method acquireRead, 
but without it, the lock becomes highly unfair.) Method release 
does not (and sometimes cannot) itself wake up cowaiters. This 
is done by the primary thread, but helped by any other threads 
with nothing better to do in methods acquireRead and 
acquireWrite. 

 These rules apply to threads actually queued. All tryLock forms 
opportunistically try to acquire locks regardless of preference 
rules, and so may "barge" their way in.  Randomized spinning 
is used in the acquire methods to reduce (increasingly 
expensive) context switching while also avoiding sustained 
memory thrashing among many threads.  We limit spins to the 
head of queue. A thread spin-waits up to SPINS times (where 
each iteration decreases spin count with 50% probability) 
before blocking. If, upon wakening it fails to obtain lock, and is 
still (or becomes) the first waiting thread (which indicates that 
some other thread barged and obtained lock), it escalates spins 
(up to MAX_HEAD_SPINS) to reduce the likelihood of 
continually losing to barging threads. 

 Nearly all of these mechanics are carried out in methods 
acquireWrite and acquireRead, that, as typical of such code, 
sprawl out because actions and retries rely on consistent sets 
of locally cached reads. 

 As noted in Boehm's paper (above), sequence validation 
(mainly method validate()) requires stricter ordering rules than 
apply to normal volatile reads (of "state").  To force orderings of 
reads before a validation and the validation itself in those cases 
where this is not already forced, we use Unsafe.loadFence. 

 The memory layout keeps lock state and queue pointers 
together (normally on the same cache line). This usually works 
well for read-mostly loads. In most other cases, the natural 
tendency of adaptive-spin CLH locks to reduce memory 
contention lessens motivation to further spread out contended 
locations, but might be subject to future improvements.

該設計採用了序列鎖Sequence locks的元素(在linux內核中使用;參見Lameter的http://www.lameter.com/gelato2005.pdf和其他地方;參見Boehm的http://www.hpl.hp.com/techreports/2012/ HPL-2012-68.html)和有序RW鎖Ordered RW locks(參見Shirako等人http://dl.acm.org/citation.cfm?id=2312015

從概念上講,鎖的主要狀態包括在寫鎖定時甚至是其他情況下奇數的序列號。但是,讀鎖定時被非0的讀計數所抵消。驗證“樂觀”seqlock-reader-style stamps時,將忽略讀計數。因爲我們必須爲讀線程使用小的有限數量的位(當前爲7位),所以當讀線程的數量超過count時,將使用補充的讀線程溢出字。我們通過將最大讀線程count值(RBITS)視爲保護溢出更新的自旋鎖來實現此目的。

等待線程使用在AbstractQueuedSynchronizer中使用的CLH鎖的修改形式,其中每個節點都標記爲(字段模式)作爲讀線程或寫線程。等待讀線程的集合組隊(鏈接)放在一個公共節點(字段cowait)下,因此就大多數CLH機制而言充當單個節點。憑藉隊列結構,等待節點實際上不需要攜帶序列號;我們知道每個都比它的前驅更大。這將調度策略簡化爲FIFO方案,該方案包含Phase-Fair鎖的元素(參見Brandenburg&Anderson,尤其是http://www.cs.unc.edu/~bbb/diss/)。特別是,我們使用phase-fair不允許插隊規則:如果在保持讀取鎖定時傳入的讀線程但是有一個排隊的寫線程,則此傳入的讀線程將排隊。 (這個規則導致了一些方法acquireRead的複雜性,但沒有它,鎖將變得非常不公平。)方法release本身不會(有時不能)喚醒cowaiters。這是由主線程完成的,其他在方法acquireRead和acquireWrite中沒有更好的事情可以做的任何線程可以進行協助。

這些規則適用於實際排隊的線程。無論偏好規則如何,所有tryLock形式都會機會性地嘗試獲取鎖定,因此可能會插隊。在acquire方法中使用隨機自旋來減少(越來越昂貴)上下文切換,同時還避免多線程間持續的內存抖動。我們將自旋限制在隊列的頭部。在阻塞之前,線程自旋等待SPINS次數(其中每次迭代以50%的概率減少自旋計數)。如果在喚醒後無法獲得鎖,並仍然(或成爲)第一個等待線程(表示某個其他線程插隊並獲得鎖),則會提升自旋次數(最多爲MAX_HEAD_SPINS)以減少因爲插隊線程不斷丟失的獲取鎖的可能。

幾乎所有這些機制都是在acquireWrite和acquireRead方法中執行的,這些代碼通常會擴散,因爲操作和重試依賴於一致的本地緩存讀取集。

正如Boehm的論文(上文)所述,序列驗證(主要是方法validate())需要更嚴格的排序規則,而不是適用於普通的volatile讀取(“state”)。要在驗證之前強制讀取順序,並在尚未強制執行驗證的情況下強制驗證,我們使用Unsafe.loadFence。

內存佈局將鎖定狀態和隊列指針保持在一起(通常在同一緩存行上)。這通常適用於大多數讀取加載。在大多數其他情況下,自適應自旋CLH鎖定以減少內存爭用的自然趨勢減少了進一步擴展競爭位置的動力,但可能受到未來改進的影響。

3.源碼分析

3.1 構造器

    /**
     * Creates a new lock, initially in unlocked state.
     */
    public StampedLock() {
        state = ORIGIN;
    }
    /** Head of CLH queue */
    private transient volatile WNode whead;
    /** Tail (last) of CLH queue */
    private transient volatile WNode wtail;

    // views
    transient ReadLockView readLockView;
    transient WriteLockView writeLockView;
    transient ReadWriteLockView readWriteLockView;

    /** Lock sequence/state */
    private transient volatile long state;
    /** extra reader count when state read count saturated */
    private transient int readerOverflow;
    // Values for lock state and stamp operations
    private static final long RUNIT = 1L;
    private static final long WBIT  = 1L << LG_READERS;
    private static final long RBITS = WBIT - 1L;
    private static final long RFULL = RBITS - 1L;
    private static final long ABITS = RBITS | WBIT;
    private static final long SBITS = ~RBITS; // note overlap with ABITS

    // Initial value for lock state; avoid failure value zero
    private static final long ORIGIN = WBIT << 1;

3.2 寫鎖

    /**
     * Exclusively acquires the lock, blocking if necessary
     * until available.
     *
     * @return a stamp that can be used to unlock or convert mode
     */
    public long writeLock() {
        long s, next;  // bypass acquireWrite in fully unlocked case only
        return ((((s = state) & ABITS) == 0L &&
                 U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
                next : acquireWrite(false, 0L));
    }

當完全沒有加鎖時,繞過acquireWrite。

    private long acquireWrite(boolean interruptible, long deadline) {
        WNode node = null, p;
        for (int spins = -1;;) { // spin while enqueuing
            long m, s, ns;
            if ((m = (s = state) & ABITS) == 0L) {
                if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT))
                    return ns;
            }
            else if (spins < 0)
                spins = (m == WBIT && wtail == whead) ? SPINS : 0;
            else if (spins > 0) {
                if (LockSupport.nextSecondarySeed() >= 0)
                    --spins;
            }
            else if ((p = wtail) == null) { // initialize queue
                WNode hd = new WNode(WMODE, null);
                if (U.compareAndSwapObject(this, WHEAD, null, hd))
                    wtail = hd;
            }
            else if (node == null)
                node = new WNode(WMODE, p);
            else if (node.prev != p)
                node.prev = p;
            else if (U.compareAndSwapObject(this, WTAIL, p, node)) {
                p.next = node;
                break;
            }
        }

        for (int spins = -1;;) {
            WNode h, np, pp; int ps;
            if ((h = whead) == p) {
                if (spins < 0)
                    spins = HEAD_SPINS;
                else if (spins < MAX_HEAD_SPINS)
                    spins <<= 1;
                for (int k = spins;;) { // spin at head
                    long s, ns;
                    if (((s = state) & ABITS) == 0L) {
                        if (U.compareAndSwapLong(this, STATE, s,
                                                 ns = s + WBIT)) {
                            whead = node;
                            node.prev = null;
                            return ns;
                        }
                    }
                    else if (LockSupport.nextSecondarySeed() >= 0 &&
                             --k <= 0)
                        break;
                }
            }
            else if (h != null) { // help release stale waiters
                WNode c; Thread w;
                while ((c = h.cowait) != null) {
                    if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
                        (w = c.thread) != null)
                        U.unpark(w);
                }
            }
            if (whead == h) {
                if ((np = node.prev) != p) {
                    if (np != null)
                        (p = np).next = node;   // stale
                }
                else if ((ps = p.status) == 0)
                    U.compareAndSwapInt(p, WSTATUS, 0, WAITING);
                else if (ps == CANCELLED) {
                    if ((pp = p.prev) != null) {
                        node.prev = pp;
                        pp.next = node;
                    }
                }
                else {
                    long time; // 0 argument to park means no timeout
                    if (deadline == 0L)
                        time = 0L;
                    else if ((time = deadline - System.nanoTime()) <= 0L)
                        return cancelWaiter(node, node, false);
                    Thread wt = Thread.currentThread();
                    U.putObject(wt, PARKBLOCKER, this);
                    node.thread = wt;
                    if (p.status < 0 && (p != h || (state & ABITS) != 0L) &&
                        whead == h && node.prev == p)
                        U.park(false, time);  // emulate LockSupport.park
                    node.thread = null;
                    U.putObject(wt, PARKBLOCKER, null);
                    if (interruptible && Thread.interrupted())
                        return cancelWaiter(node, node, true);
                }
            }
        }
    }

在該方法中複合了很多特性:

  • 1)首先是入隊自旋,並放到隊列尾部
  • 2)如果隊列中只剩下一個結點,則在隊頭進一步自旋
  • 3)會幫助release隊頭的cowait鏈表
  • 4)最後會進入阻塞
    /**
     * If the lock state matches the given stamp, releases the
     * exclusive lock.
     *
     * @param stamp a stamp returned by a write-lock operation
     * @throws IllegalMonitorStateException if the stamp does
     * not match the current state of this lock
     */
    public void unlockWrite(long stamp) {
        WNode h;
        if (state != stamp || (stamp & WBIT) == 0L)
            throw new IllegalMonitorStateException();
        state = (stamp += WBIT) == 0L ? ORIGIN : stamp;
        if ((h = whead) != null && h.status != 0)
            release(h);
    }
    /**
     * Wakes up the successor of h (normally whead). This is normally
     * just h.next, but may require traversal from wtail if next
     * pointers are lagging. This may fail to wake up an acquiring
     * thread when one or more have been cancelled, but the cancel
     * methods themselves provide extra safeguards to ensure liveness.
     */
    private void release(WNode h) {
        if (h != null) {
            WNode q; Thread w;
            U.compareAndSwapInt(h, WSTATUS, WAITING, 0);
            if ((q = h.next) == null || q.status == CANCELLED) {
                for (WNode t = wtail; t != null && t != h; t = t.prev)
                    if (t.status <= 0)
                        q = t;
            }
            if (q != null && (w = q.thread) != null)
                U.unpark(w);
        }
    }

喚醒隊頭的後繼。

3.3 讀鎖

    /**
     * Non-exclusively acquires the lock, blocking if necessary
     * until available.
     *
     * @return a stamp that can be used to unlock or convert mode
     */
    public long readLock() {
        long s = state, next;  // bypass acquireRead on common uncontended case
        return ((whead == wtail && (s & ABITS) < RFULL &&
                 U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
                next : acquireRead(false, 0L));
    }

在無競爭的情況下直接跳過acquireRead。

    private long acquireRead(boolean interruptible, long deadline) {
        WNode node = null, p;
        for (int spins = -1;;) {
            WNode h;
            if ((h = whead) == (p = wtail)) {
                for (long m, s, ns;;) {
                    if ((m = (s = state) & ABITS) < RFULL ?
                        U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) :
                        (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L))
                        return ns;
                    else if (m >= WBIT) {
                        if (spins > 0) {
                            if (LockSupport.nextSecondarySeed() >= 0)
                                --spins;
                        }
                        else {
                            if (spins == 0) {
                                WNode nh = whead, np = wtail;
                                if ((nh == h && np == p) || (h = nh) != (p = np))
                                    break;
                            }
                            spins = SPINS;
                        }
                    }
                }
            }
            if (p == null) { // initialize queue
                WNode hd = new WNode(WMODE, null);
                if (U.compareAndSwapObject(this, WHEAD, null, hd))
                    wtail = hd;
            }
            else if (node == null)
                node = new WNode(RMODE, p);
            else if (h == p || p.mode != RMODE) {
                if (node.prev != p)
                    node.prev = p;
                else if (U.compareAndSwapObject(this, WTAIL, p, node)) {
                    p.next = node;
                    break;
                }
            }
            else if (!U.compareAndSwapObject(p, WCOWAIT,
                                             node.cowait = p.cowait, node))
                node.cowait = null;
            else {
                for (;;) {
                    WNode pp, c; Thread w;
                    if ((h = whead) != null && (c = h.cowait) != null &&
                        U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
                        (w = c.thread) != null) // help release
                        U.unpark(w);
                    if (h == (pp = p.prev) || h == p || pp == null) {
                        long m, s, ns;
                        do {
                            if ((m = (s = state) & ABITS) < RFULL ?
                                U.compareAndSwapLong(this, STATE, s,
                                                     ns = s + RUNIT) :
                                (m < WBIT &&
                                 (ns = tryIncReaderOverflow(s)) != 0L))
                                return ns;
                        } while (m < WBIT);
                    }
                    if (whead == h && p.prev == pp) {
                        long time;
                        if (pp == null || h == p || p.status > 0) {
                            node = null; // throw away
                            break;
                        }
                        if (deadline == 0L)
                            time = 0L;
                        else if ((time = deadline - System.nanoTime()) <= 0L)
                            return cancelWaiter(node, p, false);
                        Thread wt = Thread.currentThread();
                        U.putObject(wt, PARKBLOCKER, this);
                        node.thread = wt;
                        if ((h != pp || (state & ABITS) == WBIT) &&
                            whead == h && p.prev == pp)
                            U.park(false, time);
                        node.thread = null;
                        U.putObject(wt, PARKBLOCKER, null);
                        if (interruptible && Thread.interrupted())
                            return cancelWaiter(node, p, true);
                    }
                }
            }
        }

        for (int spins = -1;;) {
            WNode h, np, pp; int ps;
            if ((h = whead) == p) {
                if (spins < 0)
                    spins = HEAD_SPINS;
                else if (spins < MAX_HEAD_SPINS)
                    spins <<= 1;
                for (int k = spins;;) { // spin at head
                    long m, s, ns;
                    if ((m = (s = state) & ABITS) < RFULL ?
                        U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) :
                        (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) {
                        WNode c; Thread w;
                        whead = node;
                        node.prev = null;
                        while ((c = node.cowait) != null) {
                            if (U.compareAndSwapObject(node, WCOWAIT,
                                                       c, c.cowait) &&
                                (w = c.thread) != null)
                                U.unpark(w);
                        }
                        return ns;
                    }
                    else if (m >= WBIT &&
                             LockSupport.nextSecondarySeed() >= 0 && --k <= 0)
                        break;
                }
            }
            else if (h != null) {
                WNode c; Thread w;
                while ((c = h.cowait) != null) {
                    if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
                        (w = c.thread) != null)
                        U.unpark(w);
                }
            }
            if (whead == h) {
                if ((np = node.prev) != p) {
                    if (np != null)
                        (p = np).next = node;   // stale
                }
                else if ((ps = p.status) == 0)
                    U.compareAndSwapInt(p, WSTATUS, 0, WAITING);
                else if (ps == CANCELLED) {
                    if ((pp = p.prev) != null) {
                        node.prev = pp;
                        pp.next = node;
                    }
                }
                else {
                    long time;
                    if (deadline == 0L)
                        time = 0L;
                    else if ((time = deadline - System.nanoTime()) <= 0L)
                        return cancelWaiter(node, node, false);
                    Thread wt = Thread.currentThread();
                    U.putObject(wt, PARKBLOCKER, this);
                    node.thread = wt;
                    if (p.status < 0 &&
                        (p != h || (state & ABITS) == WBIT) &&
                        whead == h && node.prev == p)
                        U.park(false, time);
                    node.thread = null;
                    U.putObject(wt, PARKBLOCKER, null);
                    if (interruptible && Thread.interrupted())
                        return cancelWaiter(node, node, true);
                }
            }
        }
    }

同理,acquireRead複合了很多機制:

  • 1)首先是入隊自旋,如果隊尾不是讀模式則放到隊列尾部,如果是讀模式,則放到隊尾的cowait中。
  • 2)如果隊列中只剩下一個結點,則在隊頭進一步自旋
  • 3)會幫助release隊頭的cowait鏈表
  • 4)最後會進入阻塞
    /**
     * If the lock state matches the given stamp, releases the
     * non-exclusive lock.
     *
     * @param stamp a stamp returned by a read-lock operation
     * @throws IllegalMonitorStateException if the stamp does
     * not match the current state of this lock
     */
    public void unlockRead(long stamp) {
        long s, m; WNode h;
        for (;;) {
            if (((s = state) & SBITS) != (stamp & SBITS) ||
                (stamp & ABITS) == 0L || (m = s & ABITS) == 0L || m == WBIT)
                throw new IllegalMonitorStateException();
            if (m < RFULL) {
                if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) {
                    if (m == RUNIT && (h = whead) != null && h.status != 0)
                        release(h);
                    break;
                }
            }
            else if (tryDecReaderOverflow(s) != 0L)
                break;
        }
    }

3.4 樂觀讀

    /**
     * Returns a stamp that can later be validated, or zero
     * if exclusively locked.
     *
     * @return a stamp, or zero if exclusively locked
     */
    public long tryOptimisticRead() {
        long s;
        return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
    }

如果是被寫鎖獨佔,則返回0。

    /**
     * Returns true if the lock has not been exclusively acquired
     * since issuance of the given stamp. Always returns false if the
     * stamp is zero. Always returns true if the stamp represents a
     * currently held lock. Invoking this method with a value not
     * obtained from {@link #tryOptimisticRead} or a locking method
     * for this lock has no defined effect or result.
     *
     * @param stamp a stamp
     * @return {@code true} if the lock has not been exclusively acquired
     * since issuance of the given stamp; else false
     */
    public boolean validate(long stamp) {
        U.loadFence();
        return (stamp & SBITS) == (state & SBITS);
    }

SBITS只關心state有沒有被寫鎖獨佔獲取,如果沒有,則返回true。(SBITS低位全爲0,只獲取高位值。低位爲讀鎖標記,高位爲寫鎖標記。)

3.5 鎖轉換

    /**
     * If the lock state matches the given stamp, performs one of
     * the following actions. If the stamp represents holding a write
     * lock, returns it.  Or, if a read lock, if the write lock is
     * available, releases the read lock and returns a write stamp.
     * Or, if an optimistic read, returns a write stamp only if
     * immediately available. This method returns zero in all other
     * cases.
     *
     * @param stamp a stamp
     * @return a valid write stamp, or zero on failure
     */
    public long tryConvertToWriteLock(long stamp) {
        long a = stamp & ABITS, m, s, next;
        while (((s = state) & SBITS) == (stamp & SBITS)) {
            if ((m = s & ABITS) == 0L) {
                if (a != 0L)
                    break;
                if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT))
                    return next;
            }
            else if (m == WBIT) {
                if (a != m)
                    break;
                return stamp;
            }
            else if (m == RUNIT && a != 0L) {
                if (U.compareAndSwapLong(this, STATE, s,
                                         next = s - RUNIT + WBIT))
                    return next;
            }
            else
                break;
        }
        return 0L;
    }

方法tryConvertToWriteLock(long)嘗試“升級”模式並返回一個有效的寫stamp,如果

  • 1)已經處於寫模式
  • 2)處於讀模式並且沒有其他讀線程,則釋放讀鎖
  • 3)處於樂觀模式並且鎖是可用的

其他情況返回0。

tryConvertToReadLock、tryConvertToOptimisticRead類似。

   /**
     * If the lock state matches the given stamp, releases the
     * corresponding mode of the lock.
     *
     * @param stamp a stamp returned by a lock operation
     * @throws IllegalMonitorStateException if the stamp does
     * not match the current state of this lock
     */
    public void unlock(long stamp) {
        long a = stamp & ABITS, m, s; WNode h;
        while (((s = state) & SBITS) == (stamp & SBITS)) {
            if ((m = s & ABITS) == 0L)
                break;
            else if (m == WBIT) {
                if (a != m)
                    break;
                state = (s += WBIT) == 0L ? ORIGIN : s;
                if ((h = whead) != null && h.status != 0)
                    release(h);
                return;
            }
            else if (a == 0L || a >= WBIT)
                break;
            else if (m < RFULL) {
                if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) {
                    if (m == RUNIT && (h = whead) != null && h.status != 0)
                        release(h);
                    return;
                }
            }
            else if (tryDecReaderOverflow(s) != 0L)
                return;
        }
        throw new IllegalMonitorStateException();
    }

4.總結

StampedLock的等待隊列與RRW的CLH隊列相比,有以下特點:

  • 當入隊一個線程時,如果隊尾是讀結點,不會直接鏈接到隊尾,而是鏈接到該讀結點的cowait鏈中,cowait鏈本質是一個棧;
  • 當入隊一個線程時,如果隊尾是寫結點,則直接鏈接到隊尾;
  • QS類似喚醒線程的規則和A,都是首先喚醒隊首結點。區別是StampedLock中,當喚醒的結點是讀結點時,會喚醒該讀結點的cowait鏈中的所有讀結點(順序和入棧順序相反,也就是後進先出)。
  • 另外,StampedLock使用時要特別小心,避免鎖重入的操作,在使用樂觀讀鎖時也需要遵循相應的調用模板,防止出現數據不一致的問題。


參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章