基于版本jdk1.7.0_80
java.util.concurrent.locks.ReentrantLock
代码如下
/* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package java.util.concurrent.locks; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; /** * A reentrant mutual exclusion {@link Lock} with the same basic * behavior and semantics as the implicit monitor lock accessed using * {@code synchronized} methods and statements, but with extended * capabilities. * * <p>A {@code ReentrantLock} is <em>owned</em> by the thread last * successfully locking, but not yet unlocking it. A thread invoking * {@code lock} will return, successfully acquiring the lock, when * the lock is not owned by another thread. The method will return * immediately if the current thread already owns the lock. This can * be checked using methods {@link #isHeldByCurrentThread}, and {@link * #getHoldCount}. * * <p>The constructor for this class accepts an optional * <em>fairness</em> parameter. When set {@code true}, under * contention, locks favor granting access to the longest-waiting * thread. Otherwise this lock does not guarantee any particular * access order. Programs using fair locks accessed by many threads * may display lower overall throughput (i.e., are slower; often much * slower) than those using the default setting, but have smaller * variances in times to obtain locks and guarantee lack of * starvation. Note however, that fairness of locks does not guarantee * fairness of thread scheduling. Thus, one of many threads using a * fair lock may obtain it multiple times in succession while other * active threads are not progressing and not currently holding the * lock. * Also note that the untimed {@link #tryLock() tryLock} method does not * honor the fairness setting. It will succeed if the lock * is available even if other threads are waiting. * * <p>It is recommended practice to <em>always</em> immediately * follow a call to {@code lock} with a {@code try} block, most * typically in a before/after construction such as: * * <pre> * class X { * private final ReentrantLock lock = new ReentrantLock(); * // ... * * public void m() { * lock.lock(); // block until condition holds * try { * // ... method body * } finally { * lock.unlock() * } * } * } * </pre> * * <p>In addition to implementing the {@link Lock} interface, this * class defines methods {@code isLocked} and * {@code getLockQueueLength}, as well as some associated * {@code protected} access methods that may be useful for * instrumentation and monitoring. * * <p>Serialization of this class behaves in the same way as built-in * locks: a deserialized lock is in the unlocked state, regardless of * its state when serialized. * * <p>This lock supports a maximum of 2147483647 recursive locks by * the same thread. Attempts to exceed this limit result in * {@link Error} throws from locking methods. * * @since 1.5 * @author Doug Lea */ public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; /** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */ abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */ abstract void lock(); /** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { 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(); } final ConditionObject newCondition() { return new ConditionObject(); } // Methods relayed from outer class final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } /** * Reconstitutes this lock instance from a stream. * @param s the stream */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } } /** * Sync object for non-fair locks */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } /** * Sync object for fair locks */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } /** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } /** * Acquires the lock. * * <p>Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * * <p>If the current thread already holds the lock then the hold * count is incremented by one and the method returns immediately. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until the lock has been acquired, * at which time the lock hold count is set to one. */ public void lock() { sync.lock(); } /** * Acquires the lock unless the current thread is * {@linkplain Thread#interrupt interrupted}. * * <p>Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * * <p>If the current thread already holds this lock then the hold count * is incremented by one and the method returns immediately. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until one of two things happens: * * <ul> * * <li>The lock is acquired by the current thread; or * * <li>Some other thread {@linkplain Thread#interrupt interrupts} the * current thread. * * </ul> * * <p>If the lock is acquired by the current thread then the lock hold * count is set to one. * * <p>If the current thread: * * <ul> * * <li>has its interrupted status set on entry to this method; or * * <li>is {@linkplain Thread#interrupt interrupted} while acquiring * the lock, * * </ul> * * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * <p>In this implementation, as this method is an explicit * interruption point, preference is given to responding to the * interrupt over normal or reentrant acquisition of the lock. * * @throws InterruptedException if the current thread is interrupted */ public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } /** * Acquires the lock only if it is not held by another thread at the time * of invocation. * * <p>Acquires the lock if it is not held by another thread and * returns immediately with the value {@code true}, setting the * lock hold count to one. Even when this lock has been set to use a * fair ordering policy, a call to {@code tryLock()} <em>will</em> * immediately acquire the lock if it is available, whether or not * other threads are currently waiting for the lock. * This "barging" behavior can be useful in certain * circumstances, even though it breaks fairness. If you want to honor * the fairness setting for this lock, then use * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) } * which is almost equivalent (it also detects interruption). * * <p> If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true}. * * <p>If the lock is held by another thread then this method will return * immediately with the value {@code false}. * * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} otherwise */ public boolean tryLock() { return sync.nonfairTryAcquire(1); } /** * Acquires the lock if it is not held by another thread within the given * waiting time and the current thread has not been * {@linkplain Thread#interrupt interrupted}. * * <p>Acquires the lock if it is not held by another thread and returns * immediately with the value {@code true}, setting the lock hold count * to one. If this lock has been set to use a fair ordering policy then * an available lock <em>will not</em> be acquired if any other threads * are waiting for the lock. This is in contrast to the {@link #tryLock()} * method. If you want a timed {@code tryLock} that does permit barging on * a fair lock then combine the timed and un-timed forms together: * * <pre>if (lock.tryLock() || lock.tryLock(timeout, unit) ) { ... } * </pre> * * <p>If the current thread * already holds this lock then the hold count is incremented by one and * the method returns {@code true}. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until one of three things happens: * * <ul> * * <li>The lock is acquired by the current thread; or * * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or * * <li>The specified waiting time elapses * * </ul> * * <p>If the lock is acquired then the value {@code true} is returned and * the lock hold count is set to one. * * <p>If the current thread: * * <ul> * * <li>has its interrupted status set on entry to this method; or * * <li>is {@linkplain Thread#interrupt interrupted} while * acquiring the lock, * * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * <p>If the specified waiting time elapses then the value {@code false} * is returned. If the time is less than or equal to zero, the method * will not wait at all. * * <p>In this implementation, as this method is an explicit * interruption point, preference is given to responding to the * interrupt over normal or reentrant acquisition of the lock, and * over reporting the elapse of the waiting time. * * @param timeout the time to wait for the lock * @param unit the time unit of the timeout argument * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} if the waiting time elapsed before * the lock could be acquired * @throws InterruptedException if the current thread is interrupted * @throws NullPointerException if the time unit is null * */ public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } /** * Attempts to release this lock. * * <p>If the current thread is the holder of this lock then the hold * count is decremented. If the hold count is now zero then the lock * is released. If the current thread is not the holder of this * lock then {@link IllegalMonitorStateException} is thrown. * * @throws IllegalMonitorStateException if the current thread does not * hold this lock */ public void unlock() { sync.release(1); } /** * Returns a {@link Condition} instance for use with this * {@link Lock} instance. * * <p>The returned {@link Condition} instance supports the same * usages as do the {@link Object} monitor methods ({@link * Object#wait() wait}, {@link Object#notify notify}, and {@link * Object#notifyAll notifyAll}) when used with the built-in * monitor lock. * * <ul> * * <li>If this lock is not held when any of the {@link Condition} * {@linkplain Condition#await() waiting} or {@linkplain * Condition#signal signalling} methods are called, then an {@link * IllegalMonitorStateException} is thrown. * * <li>When the condition {@linkplain Condition#await() waiting} * methods are called the lock is released and, before they * return, the lock is reacquired and the lock hold count restored * to what it was when the method was called. * * <li>If a thread is {@linkplain Thread#interrupt interrupted} * while waiting then the wait will terminate, an {@link * InterruptedException} will be thrown, and the thread's * interrupted status will be cleared. * * <li> Waiting threads are signalled in FIFO order. * * <li>The ordering of lock reacquisition for threads returning * from waiting methods is the same as for threads initially * acquiring the lock, which is in the default case not specified, * but for <em>fair</em> locks favors those threads that have been * waiting the longest. * * </ul> * * @return the Condition object */ public Condition newCondition() { return sync.newCondition(); } /** * Queries the number of holds on this lock by the current thread. * * <p>A thread has a hold on a lock for each lock action that is not * matched by an unlock action. * * <p>The hold count information is typically only used for testing and * debugging purposes. For example, if a certain section of code should * not be entered with the lock already held then we can assert that * fact: * * <pre> * class X { * ReentrantLock lock = new ReentrantLock(); * // ... * public void m() { * assert lock.getHoldCount() == 0; * lock.lock(); * try { * // ... method body * } finally { * lock.unlock(); * } * } * } * </pre> * * @return the number of holds on this lock by the current thread, * or zero if this lock is not held by the current thread */ public int getHoldCount() { return sync.getHoldCount(); } /** * Queries if this lock is held by the current thread. * * <p>Analogous to the {@link Thread#holdsLock} method for built-in * monitor locks, this method is typically used for debugging and * testing. For example, a method that should only be called while * a lock is held can assert that this is the case: * * <pre> * class X { * ReentrantLock lock = new ReentrantLock(); * // ... * * public void m() { * assert lock.isHeldByCurrentThread(); * // ... method body * } * } * </pre> * * <p>It can also be used to ensure that a reentrant lock is used * in a non-reentrant manner, for example: * * <pre> * class X { * ReentrantLock lock = new ReentrantLock(); * // ... * * public void m() { * assert !lock.isHeldByCurrentThread(); * lock.lock(); * try { * // ... method body * } finally { * lock.unlock(); * } * } * } * </pre> * * @return {@code true} if current thread holds this lock and * {@code false} otherwise */ public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } /** * Queries if this lock is held by any thread. This method is * designed for use in monitoring of the system state, * not for synchronization control. * * @return {@code true} if any thread holds this lock and * {@code false} otherwise */ public boolean isLocked() { return sync.isLocked(); } /** * Returns {@code true} if this lock has fairness set true. * * @return {@code true} if this lock has fairness set true */ public final boolean isFair() { return sync instanceof FairSync; } /** * Returns the thread that currently owns this lock, or * {@code null} if not owned. When this method is called by a * thread that is not the owner, the return value reflects a * best-effort approximation of current lock status. For example, * the owner may be momentarily {@code null} even if there are * threads trying to acquire the lock but have not yet done so. * This method is designed to facilitate construction of * subclasses that provide more extensive lock monitoring * facilities. * * @return the owner, or {@code null} if not owned */ protected Thread getOwner() { return sync.getOwner(); } /** * Queries whether any threads are waiting to acquire this lock. Note that * because cancellations may occur at any time, a {@code true} * return does not guarantee that any other thread will ever * acquire this lock. This method is designed primarily for use in * monitoring of the system state. * * @return {@code true} if there may be other threads waiting to * acquire the lock */ public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } /** * Queries whether the given thread is waiting to acquire this * lock. Note that because cancellations may occur at any time, a * {@code true} return does not guarantee that this thread * will ever acquire this lock. This method is designed primarily for use * in monitoring of the system state. * * @param thread the thread * @return {@code true} if the given thread is queued waiting for this lock * @throws NullPointerException if the thread is null */ public final boolean hasQueuedThread(Thread thread) { return sync.isQueued(thread); } /** * Returns an estimate of the number of threads waiting to * acquire this lock. The value is only an estimate because the number of * threads may change dynamically while this method traverses * internal data structures. This method is designed for use in * monitoring of the system state, not for synchronization * control. * * @return the estimated number of threads waiting for this lock */ public final int getQueueLength() { return sync.getQueueLength(); } /** * Returns a collection containing threads that may be waiting to * acquire this lock. Because the actual set of threads may change * dynamically while constructing this result, the returned * collection is only a best-effort estimate. The elements of the * returned collection are in no particular order. This method is * designed to facilitate construction of subclasses that provide * more extensive monitoring facilities. * * @return the collection of threads */ protected Collection<Thread> getQueuedThreads() { return sync.getQueuedThreads(); } /** * Queries whether any threads are waiting on the given condition * associated with this lock. Note that because timeouts and * interrupts may occur at any time, a {@code true} return does * not guarantee that a future {@code signal} will awaken any * threads. This method is designed primarily for use in * monitoring of the system state. * * @param condition the condition * @return {@code true} if there are any waiting threads * @throws IllegalMonitorStateException if this lock is not held * @throws IllegalArgumentException if the given condition is * not associated with this lock * @throws NullPointerException if the condition is null */ public boolean hasWaiters(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); } /** * Returns an estimate of the number of threads waiting on the * given condition associated with this lock. Note that because * timeouts and interrupts may occur at any time, the estimate * serves only as an upper bound on the actual number of waiters. * This method is designed for use in monitoring of the system * state, not for synchronization control. * * @param condition the condition * @return the estimated number of waiting threads * @throws IllegalMonitorStateException if this lock is not held * @throws IllegalArgumentException if the given condition is * not associated with this lock * @throws NullPointerException if the condition is null */ public int getWaitQueueLength(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); } /** * Returns a collection containing those threads that may be * waiting on the given condition associated with this lock. * Because the actual set of threads may change dynamically while * constructing this result, the returned collection is only a * best-effort estimate. The elements of the returned collection * are in no particular order. This method is designed to * facilitate construction of subclasses that provide more * extensive condition monitoring facilities. * * @param condition the condition * @return the collection of threads * @throws IllegalMonitorStateException if this lock is not held * @throws IllegalArgumentException if the given condition is * not associated with this lock * @throws NullPointerException if the condition is null */ protected Collection<Thread> getWaitingThreads(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); } /** * Returns a string identifying this lock, as well as its lock state. * The state, in brackets, includes either the String {@code "Unlocked"} * or the String {@code "Locked by"} followed by the * {@linkplain Thread#getName name} of the owning thread. * * @return a string identifying this lock, as well as its lock state */ public String toString() { Thread o = sync.getOwner(); return super.toString() + ((o == null) ? "[Unlocked]" : "[Locked by thread " + o.getName() + "]"); } }
770行,不多,因为关键的部分都在AQS里,ReentrantLock做的事情其实不多
0. ReentrantLock简介
ReentrantLock是一种扩展版的synchronized,synchronized能做的事情ReentrantLock都能做,synchronized不能做的事情ReentrantLock也能做。
比方说ReentrantLock可尝试获取锁(获取不到锁立即返回),可中断,可设置超时,可公平锁,这几件事情synchronized都干不了
1. ReentrantLock原理概述
ReentrantLock主要利用了AQS维护的state属性来表明锁状态(state == 0 : 无锁,state > 0 : 有锁,此时state的值等于锁重入的次数)
ReentrantLock定义了内部类FairSync与NonFairSync,都继承于内部类Sync,又继承于AQS,
其中FairSync实现了公平锁语义,NonFairSync实现了非公平锁语义
2. ReentrantLock.lock方法的执行轨迹
ReentrantLock.lock会调用sync.lock方法,默认情况下ReentrantLock是非公平的(性能高),因此会跳到NonFairSync.lock,其源码如下
/** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1))//fast path, 尝试将state从0更新到1,成功说明加锁成功,失败说明锁已经被占用,走acquire流程 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);//slow path }
上面这段代码很简单,工作线程尝试着用cas操作将state从0更新到1,更新成功则加锁成功,更新失败则走slow path
acquire方法是AQS中的,上一章中讨论过,这里还是再分析一下
AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } NonFairLock.tryAcquire() protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } Sync.nonfairTryAcquire() /** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //总是先假定Lock未被占用,直接尝试用cas操作更新state if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //如果Lock已经被当前线程占用,那说明是锁重入的情况,自加state变量即可 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //实在是没办法了, 才认为tryAcquire操作失败,开始走线程排队的流程 return false; }
上面这段代码是非公平锁的tryAcquire流程,在这段代码里,工作线程会两次尝试直接用cas操作更新state变量(从0更新到1)。
设想一下,此时如果另外有一个线程A正在占用Lock,另外一个线程B正在等待队列里排队,然后线程A释放锁,将state从1更新到0,然后unpark线程B,线程B会tryAcquire锁。但是此时工作线程刚好用cas操作将state变量更新,线程B的tryAcquire操作也就失败了。也就是说虽然线程到来的次序是A -> B -> 工作线程,但是占有锁的次序却是A -> 工作线程 -> B。
也就是说这个锁不是按先来后到次序分配的,随时可能会有插队现象发生。这就是上面的逻辑被称为非公平锁的原因
我们假设tryAcquire操作失败,有线程正在占用锁,工作线程必须去等待队列排队,也就是调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这行代码,关联的源码段落如下:
/** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ //尝试向等待队列中插入等待线程对应的节点node private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node);//直接插入失败,说明有竞争或者CLH队列没有初始化 return node; } /** * Inserts node into queue, initializing if necessary. See picture above. * @param node the node to insert * @return node's predecessor */ //无限循环,直到node插入到等待队列为止,这个操作是lock-free的 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { //在这个位置,tail已经被更新为node,node有指向oldTail的向前指针,但是oldTail指向tail的向后指针要在下一行才会被建立,也就是说在这个瞬间,从head无法向后遍历到真正的tail节点。当然,从tail向前遍历完整个等待队列是没有问题的 t.next = node; return t; } } } } /** * Acquires in exclusive uninterruptible mode for thread already in * queue. Used by condition wait methods as well as acquire. * * @param node the node * @param arg the acquire argument * @return {@code true} if interrupted while waiting */ //node对应的线程处于唤醒状态,它会先试着尝试去获取临界区资源,如果失败,可能park自身,也可能继续自旋 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)) {//node的前驱节点是head,才有去获取临界区资源的必要(因为线程可能被interrupt所唤醒) setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) &&//根据情况决定在tryAcquire失败后,当前线程是自我park还是继续自旋试图获取锁 parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
上面这些代码的大意是,工作线程创建一个节点,用无锁算法插入到AQS的等待队列的尾部,然后在一个死循环中重复调用tryAcquire方法试图获取锁。如果tryAcquire失败,则park自身(也有可能不等待继续自旋),直到被前驱线程唤醒或者被中断信号唤醒,然后继续调用tryAcquire重试获取锁。
acquireQueued方法的返回条件是工作线程成功的获取了锁。这样ReentrantLock.lock方法也就返回了。
3. ReentrantLock.unlock方法的执行轨迹
ReentrantLock.unlock /** * Attempts to release this lock. * * <p>If the current thread is the holder of this lock then the hold * count is decremented. If the hold count is now zero then the lock * is released. If the current thread is not the holder of this * lock then {@link IllegalMonitorStateException} is thrown. * * @throws IllegalMonitorStateException if the current thread does not * hold this lock */ public void unlock() { sync.release(1);//调用AQS的方法 } AbstractQueuedSynchronizer.release /** * Releases in exclusive mode. Implemented by unblocking one or * more threads if {@link #tryRelease} returns true. * This method can be used to implement method {@link Lock#unlock}. * * @param arg the release argument. This value is conveyed to * {@link #tryRelease} but is otherwise uninterpreted and * can represent anything you like. * @return the value returned from {@link #tryRelease} */ public final boolean release(int arg) { if (tryRelease(arg)) {//调用ReentrantLock.Sync.tryRelease方法,尝试释放占有的锁 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h);//如果等待队列里有线程在排队,选取排在最前面的等待节点并唤醒 return true; } return false; } ReentrantLock.Sync.tryRelease protected final boolean tryRelease(int releases) { int c = getState() - releases;//减去重入次数一次 if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程与AQS里维护的占有者线程不同,说明此线程在试图释放不属于自己的锁,抛出异常 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//锁重入次数减到0,说明这个锁跟当前线程无关了 free = true; setExclusiveOwnerThread(null);//将AQS维护的占有者线程置为null } setState(c);//更新锁重入次数 return free; } /** * Wakes up node's successor, if one exists. * * @param node the node */ private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ //如果head的向后指针指向null,就从CLH队列的尾部向前遍历,直到找到最接近头部的等待唤醒的节点 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread);//唤醒等待线程 }
大概意思是,更新AQS的state变量,如果state == 0,说明这个锁已经被当前线程完全的释放了。于是去AQS维护的等待队列里找最靠前的等待线程,并将其唤醒。 被唤醒的等待线程又会调用tryAcquire方法去尝试占用锁,这样就跳回到上一步中描述的acquireQueued方法的执行轨迹里了。
现在我们终于把ReentrantLock在默认的公平锁下的lock/unlock方法的执行轨迹分析完了。后续会接着描述非公平锁的执行轨迹,以及可中断锁与可超时锁的执行轨迹。
4. 公平锁
上面的代码是如何实现非公平锁的语义的?
关键就在NonfairSync.lock方法与NonfairSync.tryAcquire方法中,工作线程会无视AQS的等待队列中是否有线程正在等待,直接调用cas方法来尝试更新state变量的值,也就是插队直接与等待队列中的第一个等待线程竞争了。
如果此时前一个线程刚好释放了锁资源并将等待队里中的后继线程唤醒,那么后继线程很可能抢不到锁资源而只能继续park。这样就实现了非公平锁的语义了。
所以如果我们想要实现公平锁的话,就要改造NonfairSync.lock与NonfairSync.tryAcquire这两个方法
我们来看一下FairSync.lock的调用轨迹
FairSync.lock final void lock() { acquire(1);//不用cas操作抢占锁,而是直接acquire,不会有插队情况发生 } AbstractQueuedSynchronizer.acquire//这部分与NonfairSync.lock一样 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } FairSync.tryAcquire /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//如果锁暂时未被占用 if (!hasQueuedPredecessors() &&//而且AQS的等待队列中没有线程在排队 compareAndSetState(0, acquires)) {//这才尝试占有锁 setExclusiveOwnerThread(current); return true; } } //如果锁已经被占用了,检查一下是不是锁重入的情况 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;//有线程在排队,而且不是锁重入的情况,锁占用失败,老老实实去等待队列排队 } } AbstractQueuedSynchronizer.hasQueuedPredecessors public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
主要的区别是FairSync.lock方法与FairSync.tryAcquire方法只在锁不被占用 && 等待队列中没有排队线程的情况下才会去尝试占有锁。
一旦发现锁已被占用或者等待队列不为空,就去等待队列排队。
5. 可中断锁
查看ReentrantLock.lockInterruptibly方法的调用轨迹
ReentrantLock.lockInterruptibly public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } AbstractQueuedSynchronizer.acquireInterruptibly public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//如果线程已被中断,直接抛出异常 throw new InterruptedException(); if (!tryAcquire(arg))//直接尝试占有锁失败,执行doAcquireInterruptibly doAcquireInterruptibly(arg); } /** * Acquires in exclusive interruptible mode. * @param arg the acquire argument */ //这个方法是acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的可中断版本 private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //acquireQueued这里是interrupted = true throw new InterruptedException();//检测到中断直接抛出异常 } } finally { if (failed) cancelAcquire(node);//如果检测到中断,这一行会被调用,将插入等待队列的节点的状态为标记为CANCELED } }
将doAcquireInterruptibly方法与acquireQueued对比着看,就能发现区别。
acquireQueued方法中,如果检测到异常,只是将interrupted状态标记为true,然后继续自旋尝试获取锁
doAcquireInterruptibly方法中,如果检测到异常,则是直接抛出InterruptedException,退出自旋。这样就实现了可中断锁的语义
6. 可超时锁
查看ReentrantLock.tryLock方法的调用轨迹
ReentrantLock.tryLock public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } AbstractQueuedSynchronizer.tryAcquireNanos public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); } AbstractQueuedSynchronizer.doAcquireNanos /** * Acquires in exclusive timed mode. * * @param arg the acquire argument * @param nanosTimeout max wait time * @return {@code true} if acquired */ private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { long lastTime = System.nanoTime(); final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return true; } if (nanosTimeout <= 0)//如果发生锁超时,退出循环 return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)//如果距离超时不到1000ns则自旋(park开销大),如果超过1000ns,则park住 LockSupport.parkNanos(this, nanosTimeout);//park指定时间 long now = System.nanoTime(); nanosTimeout -= now - lastTime; lastTime = now; if (Thread.interrupted()) throw new InterruptedException();//如果接到interrupt信号,也抛出InterruptedException } } finally { if (failed) cancelAcquire(node); } } /** * The number of nanoseconds for which it is faster to spin * rather than to use timed park. A rough estimate suffices * to improve responsiveness with very short timeouts. */ static final long spinForTimeoutThreshold = 1000L;
很容易的可以看出,超时锁的关键在于doAcquireNanos方法,这个方法会检查距离锁超时还有多少时间,然后调用带有时间参数的LockSupport.park方法,将工作线程park指定时间。时间到期后会自旋判断是否发生锁超时。
由于park方法无法保证纳秒级别的时间精度,所以在距离锁超时不到1000ns的时候,程序会选择无限自旋直到获取锁成功或者锁超时为止。
这个设计是非常精妙的,具体为什么选择1000ns而不是其他时间,这应该也是Doug Lea大神实际测算过的均衡的结果(太小可能导致计时不精确,太大则会耗费太多CPU资源在自旋上)