您的位置:首页 > 移动开发 > Android开发

ReentrantLock实现Android版源码剖析

2014-07-03 17:59 183 查看
作为一个可重入的独占锁,ReentrantLock与隐式监控锁synchronized有着相同的行为和语义,不过ReentrantLock有着更高的扩展性。

简介

ReentrantLock由上一次成功获取锁,但还没有释放锁的线程所拥有。如果锁没有被任何线程所拥有,那么此时有线程调用lock方法则会成功获取锁并返回。另外,如果当前获取锁的线程再次调用lock方法,该方法会马上返回。这个可以通过利用方法isHeldByCurrentThread,以及getHoldCount检查。

类的构造方法有一个可选的fairness参数。如果为true,则在竞争条件下,锁的获取会倾向于等待最长的线程。否则(设为false),则当前锁的获取不会有特殊的规律。实用公平锁可能在总体上会有更加低的吞吐量(也就是说更加慢,一般是慢很多),但对比其那些使用默认配置,公平锁对于不同线程获取锁的时间上会有更少差异,并且保证减少等待饥饿的出现。尽管如此,锁的公平性不保证线程调度的公平,因此,可能会出现一条或者数条线程为了成功获取公平锁会出现多次获取,与此同时,其它活动线程只能白白等待也没法获取锁。

另外注意的是,非超时版本的tryLock方法没有提供公平性调度。该方法会在其它线程等待的情况下也可以成功获取锁。

具体实现

ReentrantLock主要在内部定义了一个Sync类,重载类AbstractQueuedSynchronizer,主要处理一些关于可重入的逻辑,然后还定义类NonfairSync,以及类FairSync,分别对应于非公平以及公平策略的锁实现。ReentrantLock的主要public接口均是依靠这几个内部类的方法来实现。

获取锁和释放锁方法如下。

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

public void unlock() {
sync.release(1);
}
sync变量在构造的时候初始化

public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
我们主要集中剖析FairSync类和NonfairSync类的实现。

(1)NonfairSync类

该类实现如下。

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
这里首先说明一下,ReentrantLock的锁状态值

1.大于等于1(大于1表示单一线程重复获取锁)表示锁已经被获取;

2.0表示锁还没有被获取;

另外我们还要回想一下acquire方法,也就是调用AbstractQueuedSynchronizer的获取锁方法,会尝试获取锁,如果失败则会进入内部的FIFO等待队列,直到能够重新获取锁为止。

lock方法的实现,首先尝试通过CAS把状态从0变为1,如果成功,则设置当前线程为独占锁的线程;如果失败,则会调用acquire重新尝试竞争获取锁。这里会有一个CAS的原因是由于这是一个非公平锁的策略决定的。

关于非公平策略,可以假设这一种情况,如果一把锁已经有几个线程在等待队列里等候获取,如果这时候锁释放的时候,刚好此时出现没有进入等待队列的线程尝试获取锁,此时就会出现竞争,非公平策略对于这种情况是允许外来线程能够插队获取(immediate barge),如果CAS成功的话,便马上获取锁,等待队列里的线程便会重新进入等待状态,这样就做成一种不公平的情况。

同样道理,我们看看tryAcquire的实现,调用的是基类的nonfairTryAcquire方法,方法实现如下。

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)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
就如名字一样nonfairTryAcquire,是一个非公平的tryAcquire版本实现,首先调用AQS的getState方法获取当前锁状态值,如果为0则调用CAS尝试更改状态值,如果成功则把当前线程设置为独占锁线程,并且返回true表示已经获取锁。如果锁状态不为0,则必须判断当前的线程是否当前被设置的独占锁线程,如果是,则表明这次是一个重入操作,重新把锁状态值加上请求值acquires,并且返回true。如果以上均失败,表示获取锁失败,需要把当前结点进入等待队列。

接下来来看看FairSync类的实现。

(2)FairSync类

所谓的公平策略,其实就是在之前说明的例子里,不允许外来线程能够插队获取锁,如果等待队列中有线程在等待获取锁,则所有外来线程必须要进入等待队列,这样就保证每次获取锁的必定是队列中第一个等待线程,避免了线程等待过长的时间。

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

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;
}
}
公平策略下的lock的实现很简单,只是调用acquire(1),去掉了之前的允许插队的CAS逻辑。然后再看看tryAcquire的实现。对比起非公平策略的版本,在获取锁的判断里增加了hasQueuedPredecessors的判断,该函数是AQS基类方法,判断当前等待队列里是否有前继结点在等待,如果有则返回true,如果没有则返回false,这样如果hasQueuedPredecessors返回false的时候,表明自己就是队列里第一个等待获取锁的线程,这样才可以顺利获取锁。

来看看hasQueuedPredecessors的实现。

public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
函数返回true的话,表明在等待队列里有前继结点;返回false表示当前线程不在等待队列,或者等待队列为空,或者等待队列里有前继结点。函数实现很简单,只有几个判断。回想一下AQS的实现里等待队列的特征,就很容易理解这个函数实现。头结点不等于尾结点表明等待队列中至少有一个线程在等待,此时如果头结点的next为null,这是因为头结点已经出队列(可以看看acquireQueued的p.next=null),因此表明有前继结点要返回true,如果不为null,则继续判断后继结点的线程,如果不为当前线程,则也要返回true。

这样,tryAcquire里添加hasQueuedPredecessors来实现来公平策略。

(3)tryRelease实现

接下来看看释放锁的时候,sync类重载tryRelease的实现。

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

函数首先计算当前锁状态值与releases(目前实现必定为1)的差值c,然后如果当前线程不是已经获取锁的独占线程则要抛出IllegalMonitorStateException异常,如果差值c刚好为0,则表示要释放锁,同时把独占线程设为null,返回true表示锁已经被释放,如果c不为0,则表示要为之前重入的一次释放,因此重新设置新的锁状态值,然后返回false。实现主要是注意一下可重入的逻辑,没有其它特别要注意的地方。

总结

到此,ReentrantLock的解析就完成了。虽然还有其它一些方法来表示当前锁状态,有兴趣可以自行深入了解,这里不再赘述。ReentrantLock要注意的地方有两个,一个就是tryAcquire和tryRelease的实现里要注意可重入逻辑的判断;另外一个就是关于公平策略。其实对于公平锁来说,由于在竞争的条件下必须强制外来线程进入等待队列,由于线程调度是不可控的,因此会大大降低了吞吐量,也使得整个锁的性能降低,但公平锁可以避免某一线程由于需要等待锁要耗费过长的时间,而导致饥饿性,适用于某些场合,因此开发者必须要按照实际情况进行取舍,选择最适合的锁来达到需求。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐