您的位置:首页 > 编程语言 > Java开发

Reentrantlock源码剖析--菜鸟一枚,鼓励指正

2016-04-28 17:26 417 查看

Reentrantlock源码剖析(未完待续)

Reentrantlock类中大约共有21个方法,三个内部类(Syn,FairSyn,NonfairSync),实现了接口Serializable,主要的加锁解锁功能,是通过三个内部类完成的。在所有的方法中涉及到加锁的方法有三个,lock(),tryLock(),tryLock(long,TimeUnit ),涉及到解锁的一个unlock().与Condition有关的共四个方法(后续介绍),其他的函数基本上是Getter函数。

加锁

流程图:



- lock()

先看一下啊lock的源码,sync是什么?查看声明, private final Sync sync;sync是Sync的一个对象,其中Sync就是前面提到的三个内部类的其中之一,也是另外两个类的父类,Sync继承了一个听说很重要的类AbstractQueuedSynchronizer,没有具体深入,待后续跟进。

public void lock() {
sync.lock();
}


lock()在Sync中是一个抽象函数,具体的定义延迟到了子类中,这样子类可以更加灵活的去实现它,这里用到了一个设计模式,模版设计模式父类声明子类实现父类调用。这里lock()函数实现的子类就是ReentrantLock中的另外两个内部类FairSyn,NonfairSync,以NonfairSync内部类进行剖析。

final void lock() {
//CAS操作,将state设为1,state是用来表示有无锁竞争
if (compareAndSetState(0, 1))

//获取锁成功,将当前的锁的所有现场设为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//获取失败调用acquire试图再次请求或者阻塞自己
}


acquire()是来自AbstractQueuedSynchronizer类具体实现如下

public final void acquire(int arg) {
//再一次尝试获取锁若成功则返回,其中tryAcquire的实现实在子类中,若获取失败则调用acquireQueued()将加入到队列中的当前线程阻塞
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}


下面是非公平锁的tryAcquire()具体实现,间接调用了nonfairTryAcquire(),首先获取state,判断当前有无线程占有该锁,c==0无竞争,通过CAS操作修改当前的state,如果c!=0在判断当前锁占有线程是不是当前线程,若是不用在获取锁,直接跟新state,此时不许用cas操作,线程安全,偏向锁。如果全部失败返回false,调用acquireQueued()将加入到队列中的当前线程阻塞。

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}


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


下面是addWaiter()的函数实现,Node类是AbstractQueuedSynchronizer的一个内部类,个人理解是用来维护整个阻塞队列的。首先通过当前线程构造一个Node,获取链表的末尾节点,判断末尾节点是否存在,若pred!=null,则CAS操作直接将node插入到末尾节点后面,这里的链表是一个双向链表。若末尾节点为空,则调用enq()函数,将当前节点加入到队列中,具体看enq函数的源码。

private Node addWaiter(Node mode) {
//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);
return node;
}


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)) {
t.next = node;
return t;
}
}
}
}


将当前线程入队列之后,因为获取不要锁,则需要将当前线程阻塞,acquireQueued()源码如下,在试图阻塞线程之前,每次都要重新获取一下锁,万一成功了呢,看似会死循环,我们来看一下shouldParkAfterFailedAcquire(p, node) 和parkAndCheckInterrupt()都做了什么

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)) {
setHead(node);
p.next = null; // help GC 设置为空帮助GC回收
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}


shouldParkAfterFailedAcquire()(ps:这里我把原来的英文注释删掉了,如果你英文好,就不要看完我在这瞎bb了,直接看原注释就行,比我说的好。)首先查看了前一个node的节点状态:

规则1:如果前继的节点状态为SIGNAL,表明当前节点需要unpark,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞

规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞

规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同

[code]private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java Reentrant lock