Know the source , konw everything!
想必大家和我一样,使用这些类的时候,如果只是知道如何使用这些工具类,那是远远不够的,你会压抑不住内心的躁动:想对类的实现逻辑和设计模式以及源码一探究竟。就像拜读欧几里得的《几何原本》一样,不管多么困难的推论,只有分解成我所熟悉的定理和推论以及公理,并证明她是对的,那么这个推论对我来说这就是透明的,不在像以前的黑盒子一样,那么这个推论对于我来说,她就不再是黑盒子和高峰,而是“巨人的肩膀”,站在上面我能看的更多更远。因此将黑盒子透明化是多么的必要了,OK,废话不多说,让我们去征服她吧!
ReentrantLock类在很多JDK源码类中都有使用,举个例子ArrayBlockingQuue阻塞队列类。
线程使用ArrayBlockingQueue时,在多线程的情况下,ArrayBlockingQueue只会运行一个线程对items(存放消息的数组)数据进行操作,其他请求操作的线程都会进入阻塞状态。
在阻塞队列类中,主要用值进队列操作和值出队列操作。且这两种队列都会在某种情况下处于阻塞等待状态。进队列操作会在队列满员的时候阻塞,出队列操作会在队列没有
值的时候发生阻塞。
看了ReentrantLock源码后,其实ReentrantLock类是一个运用了半透明的装饰模式(也可以看做是代理模式)的类,装饰的类是一个同步类 Sync。属性的代码如下:
private final Sync sync;//同步类
类Sync是一个抽象类,实现它的子类有NonfairSync 和FairSync,这两个类都是声明在类ReentrantLock类中的静态常量类(这样的类不能被继承),对于这两种类,代表着两种
不同阻塞队列同步锁的获取模式:公平式和非公平式。这两种不同的区别在于函数tryAcquire的不同实现;
在ReentrantLock类构造函数中,初始化Sync类的其中一个子类,至于如何选择,请看源代码:
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();
}
因此,Sync默认初始化子类是NonfairSync类,即不公平方式:非IFOF方式;而FairSync类阻塞队列获取锁的方式是遵循先进先出的规则的。
至于Sync类的逻辑主要是继承了抽象类AbstractQueuedSynchronizer,而类AbstractQueuedSynchronizer继承了抽象类AbstractOwnableSynchronizer,至于这个抽象类的
源码如下:AbstractOwnableSynchronizer类存放着锁的拥有线程对,即标记着锁排他模式下的拥有者。AbstractQueuedSynchronizer类实现了大部分的同步操作。这个类也是这边文章的重点分析对象。
/*
* 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;
/**
* A synchronizer that may be exclusively owned by a thread. This
* class provides a basis for creating locks and related synchronizers
* that may entail a notion of ownership. The
* <tt>AbstractOwnableSynchronizer</tt> class itself does not manage or
* use this information. However, subclasses and tools may use
* appropriately maintained values to help control and monitor access
* and provide diagnostics.
*
* @since 1.6
* @author Doug Lea
*/
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
/**
* Sets the thread that currently owns exclusive access. A
* <tt>null</tt> argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* <tt>volatile</tt> field accesses.
*/
protected final void setExclusiveOwnerThread(Thread t) {
exclusiveOwnerThread = t;
}
/**
* Returns the thread last set by
* <tt>setExclusiveOwnerThread</tt>, or <tt>null</tt> if never
* set. This method does not otherwise impose any synchronization
* or <tt>volatile</tt> field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
抽象类AbstractOwnableSynchronizer实现的操作是指向当前获得锁的线程 exclusiveOwnerThread,赋值这个引用,获取这个引用的值。
而抽象类AbstractQueuedSynchronizer实现了主要的功能。首先要明确的是这个类实现的是一个类似链表的数据结构。主要的属性都是链表数据结构中必有的属性,链表
的头和尾,以及链表的状态:
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
三个属性。
Node节点类声明在链表类中,修饰符为静态 常量。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;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
由Node类中属性volatile Thread thread;可以知道Node对象链表存放的数据是一些线程对象。因此实际上ReentrantLock类实际上是对多线程链表数据结构进行相关的操作
,由在ArrayBlockingQueue类源码(下一篇博文)可知,除了Sync链表类外,ReentrantLock类还用一个链表类ConditionObject实现了等待waiting线程的 相关操作。
小结,至此可知,客户端使用ReentrantLock类进行多线程同步控制操作,但是实际的操作是由底层的几个类实现:ConditionObject ,AbstractQueuedSynchronizer ,Node等等。
看ConditionObject类函数await的源码:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
线程阻塞的操作其实是底层代码LockSupport.park(this); 这个类是一个封装了java平台对操作系统底层访问的类Unsafe类,这个类听过了原子级别的操作,在这里主要就是用了Unsafe类的park函数和unpark函数
park函数将java线程挂机,unpark函数将线程终止挂起。LockSupport类所有版本的park方法都是调用了Unsafe类的park方法。
下面就来详细分析ReentrantLock类的底层类的一些逻辑关系及如何处理。
ok,从层次角度去分析下这些类;首先,客户端直接使用的类是ReentrantLock,实现Condition接口的ObjectCondition类。
ReentrantLock,ObjectCondition--->Sync(实现抽象类Sync的的FairSync类和NonfairSync类)--->主要功能实现类AbstractQueuedSynchronizer--->实现了指定同步锁目前拥有
者功能的抽象类AbstractOwnableSynchronizer。
因此主要实现类AbstractQueuedSynchronizer才是我们要着重分析的类。
前面我们提到了两个节点类为Node类的链表NonfairSync类(默认为线程不公平竞争的方式),当然你也可以指定FariSync公平竞争方式:ReentrantLock lock = new
ReentrantLock(true);//构造函数中会创建FariSync类作为代理对象。
class Node{
Thread thread;
Node next;
Node pre;
int waitstatus
}
每个节点都会指向节点前去和后驱,这个锁的等待链表是双向的。
链表数据结构:
class AbstractQueuedSynchronizer{
Node head;
Node tail;
int state;
}
锁等待链表如图所示:
在客户端使用ReentrantLock锁类形式:lock.lock();
......客户端操作代码......
lock.unlock();
其中lock()方法源码是NonfairSync类的lock方法,源码如下:
final void lock() {
//当Sync类创建时未初始化state的值,默认为0;因此这里state的值被修改为1,并将当前线程设置为获取锁的线程
if (compareAndSetState(0, 1))//这个是获取锁的操作,将变量exclusiveOwnerThread指向本线程,这个是原子操作
setExclusiveOwnerThread(Thread.currentThread());//获取锁线程设置为当前线程
else
acquire(1);//如果锁状态state不为0,则进入尝试获取锁方法;
}
acquire方法源码:
public final void acquire(int arg) {
//这个函数表示:线程获取锁失败,进入sync线程锁阻塞队列,
if (!tryAcquire(arg) &&// tryAcquire方法是线程尝试获取锁如果失败继续执行进入锁等待队列。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//进入锁等待队列。
selfInterrupt();
}
tryAcquire方法有两种,一种是公平竞争方式同步实现类的方法,还有一种非公平方式竞争方式实现类的方法;这里以公平方式为例:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//当Sync类创建时未初始化state的值,默认为0;
if (c == 0) {
if (!hasQueuedPredecessors() && //这个是判断获取锁的线程存在且不是当前的队列的判断
compareAndSetState(0, acquires)) {//原子操作--设置同步锁state状态属性
setExclusiveOwnerThread(current);//设置获取同步锁的线程为当前线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果获锁的线程是当前线程,则线程可以多次获取同步锁,因为对于这个线程而言,代码的执行时顺序执行,不需要锁
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);//设置同步锁state状态属性
return true;
}
return false;
}
如果线程没没有成功获取锁的话,就会进入获取同步锁的等待队列中去,然后将线程挂起。执行这两步骤的方法分别是addWaiter和acquire,这两个方法的源码及注释分析
如下:
private Node addWaiter(Node mode) {
//sync获取锁阻塞队列的请求节点是在队尾插入的。
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节点对象插入表尾。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/*
* 由enq函数的对Sync链表类的处理逻辑可言推出:
* 链表的表头引用指向的是获得了锁线程的Node对象,表尾指向的是链表最后一个节点。
* 当链表从没插入任何Node的时候,链表和表头指向null
*/
private Node enq(final Node node) {
for (;;) {//这里循环是个关键点哈,第一个if语句是处理链表为空的时候,创建一个不含任何数据的空Node节点,然后在循环返回执行else矩句中的代码。
Node t = tail;
//链表为空的时候,即链表的表头和表尾都是null
if (t == null) { // Must initialize 如果表尾引用指向为空
if (compareAndSetHead(new Node()))//如果head表头引用对象为空,则创建一个node作为表头,且表尾和表头引用都指向这个node对象。
tail = head;
} else {//如果表尾不为空值,则将node插入表尾,作为表尾。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在编辑完上面这些博文后,我看到了一个更牛的一篇博文,这个博文的格式,和角度个人觉得非常优秀,在之后我要写的博文中将会借鉴这篇优秀的博文。
ReentrantLock实现原理深入探究:http://www.cnblogs.com/xrq730/p/4979021.html
这篇博文博文最后,博主说他也不太明白公平锁和非公平锁的差别,在这里我将对这位好博客的进行我的 补充,其实公平锁和非公平锁在本篇博客已经提到:
公平锁和非公平锁的差别在于获取锁的方式不同,他们入锁方式是一样的,都是在 锁队列尾部插入,而对于获取锁的方式,公平锁是遵循先进先出规则的,而非
公平锁则是随机获取锁,公平锁和非公平锁两个类为NonfairSync和FairSync。
这两个类都继承了Sync类,锁获取方式不同体现在 tryAcquire方法。
NonfairSync方法tryAcquire调用积累Sync的nonfairTryAcquire方法,而FairSync类则实现了TryAcquire方法,这两个方法原代码如下
nonfairTryAcquire方法源代码:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (compareAndSetState(0, acquires)) {//如果锁在该线程获取锁的执行过程中被其他线程释放了,则在此另作一个判断:如果state==0锁为空的状态 ,即获取锁
if (c == 0) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果当前获取锁的线程就是当前拥有锁的线程
int nextc = c + acquires; //锁的冲入层数增加acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");//超出最大锁数
setState(nextc);//state记录竞争这个锁的线程的个数
return true;
}
return false;
}
TryAcquire方法源代码:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//当Sync类创建时未初始化state的值,默认为0;
if (c == 0) {
if (!hasQueuedPredecessors() && //这个是判断获取锁的线程存在且不是当前的队列的判断
compareAndSetState(0, acquires)) {//原子操作--设置同步锁state状态属性
setExclusiveOwnerThread(current);//设置获取同步锁的线程为当前线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果获锁的线程是当前线程,则线程可以多次获取同步锁,因为对于这个线程而言,代码的执行时顺序执行,不需要锁
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);//设置同步锁state状态属性
return true;
}
return false;
}
/*
* 这个方法的理解还需要根据同步锁队列来分析,同步锁队列为空的情况是:1.同步锁从未被获取过--head==tail==null;
* 2.同步锁被获取过,但得到了释放,因此head==tail==空的Node节点对象
*
* 这个方法返回false:同步锁队列为空或是获取锁的就是当前线程。
* 返回true:同步锁被占用的是其他线程
*/
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());
}
在TryAcquire方法中判断多加了个条件:如果不满足这个条件就开始获取锁操作,即函数hasQueuedPredecessors返回false,而返回false的情况是,阻塞队列为空
(head==tail==null或(head==tail)!=null且head.threaad==null)或者队列不为空且头结点的后驱不为空,指向线程为当前线程。
总而来说,公平锁类的等待队列中只有头节点head(head指向的是一个空节点)才能有获取锁的资格,其他线程都会被中断。而非公平锁不一样,没有这样的判断条件。
对于ReentrantLock的源码分析就到这了,这个类的核心是Lock和unLock API。