关于JUC的讲解,下边的博客写的很详细。
http://wangkuiwu.github.io/categories/#java
以下是自己学习JUC时,关于JUC锁的一点粗浅的理解。主要是ReentrantLock。
在JDK的JUC包中,大量的工具类使用到了锁,锁分为独占锁(exclusive lock)和共享锁(shared lock),独占锁又分为公平和非公平锁。这些锁都是基于一个核心类叫AQS(AbstractQueuedSynchronizer),而AbstractQueuedSynchronizer类里边有使用到CLH等待队列(队列的节点时内部类Node)和一个整形类型的叫state的属性字段来控制锁的状态。其中AQS包含head和tail的属性字段,分别指向队列的头和尾。对于独占锁(排他锁),头节点是当前持有锁的线程。此外,AQS同时也会用到乐观锁用到的CAS(CompareAndSwap)机制去给state字段赋值。
另外,线程在通过ReentrantLock.lock()获取锁失败而被block住时,是通过LockSupport.part()方法将线程设置为WAITING状态的(或者说将线程阻塞住),而解锁时,是通过LockSupport.unpark()方法解锁。
AbstractQueuedSynchronizer中的state字段以及CLH队列的head,tail
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
AbstractQueuedSynchronizer.Node源码
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
这里以ReentrantLock源码来简要分析以下。
在ReentrantLock中,有一个Sync类型的成员变量sync,Sync是定义在ReentrantLock静态内部类。而Sync是继承AQS(AbstractQueuedSynchronizer)的。
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;
...
同时,在ReentrantLock中又定义了Sync的两个子类 - FairSync和NonfairSync,就是公平锁和非公平锁。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
...
...
/**
* 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);
}
ReentrantLock提供了无参和有参构造函数,无参构造函数创建的实例默认是非公平锁,而有参构造函数可以手工指定锁的类型。
/**
* 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();
}
ReentrantLock利用AQS(AbstractQueuedSynchronizer)的CLH队列和CAS机制来实现CLH等待队列中的锁按照FIFO方式公平的获取锁还是采用非公平的方式。对于公平锁,在锁被当前持有锁的线程通过lock.unlock()方法释放时,会将处于CLH等待队列中头节点(代表当前持有锁的线程)的后边,处于等待状态的第一个节点所代表的线程给唤醒(通过LockSupport.unpark方法),使该线程处于Runnable状态。这样当操作系统做线程切换时(会按照一点算法随机的轮询所有处于Runnable状态的线程,来决定下一步运行哪个线程),如果该被激活的线程被轮询到,就可以从上次停止的地方(LockSupport.park()所在的地方)继续执行,因为AQS使用自旋的方式(具体参考AQS的acquireQueued()方法),该线程会尝试再次获取锁,此时会成功,并往下执行执行。
而对于那些处在队列后边的线程,由于他们没有被之前持有锁的线程在释放锁时调用LockSupport.unpark()去给他们唤醒,所以他们还是处于WAITING状态,是没有办法获取到操作系统的临幸的,因为操作系统在做线程轮询时,只会在那些处于RUNNABLE的线程间切换。
对于非公平锁,在获取锁时与公平锁方式不同,他不会检查在CLH队列中,是不是有排在自己前边的节点,而是直接尝试获取锁。并且非公平锁的锁释放方式根公平锁是一样的。
这样就有一个疑问,在当前持有锁的线程释放非公平锁的时候,只有队列中头节点后边的第一个有效线程会被unpark,那其他节点还是处于WAITING状态,根本就不会获取到CPU的轮询,那这不是根公平锁一样的么?难道非公平锁只是在所有等待状态的线程都被中断唤醒,从而都有机会获取锁时才体现非公平的特征?