并发和多线程(十六)--ReentrantLock源码解析
前面刚学习了AQS的基本原理,主要通过两个队列实现功能(同步队列+等待队列,前者是双向链表,实现加锁和解锁,后者是单向链表,用做同步协作,阻塞、唤醒),正好可以趁热打铁,了解一下ReentrantLock的源码,有了AQS的基础,阅读ReentrantLock的源码是非常简单的,如果没有了解AQS原理的同学,可以参考:
AbstractQueuedSynchronizer源码(上)--排他锁
AbstractQueuedSynchronizer源码(下)--共享锁和Condition条件队列
我们都知道ReentrantLock的底层就是通过AQS实现,和Synchronized一样都是可重入锁,同时也是排他锁,所以ReentrantLock只能一个线程获取锁,但是线程可以获取当前资源的多重锁,对应锁数量+1,每次释放-1,直到等于0,才可以被其他线程竞争获取锁,有点引用计数法的意思。
类定义:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//默认构造函数,得到的事非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//支持传入fair参数,得到是否公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
ReentrantLock实现Lock接口,序列化,这里并没有实现AQS接口,而是通过内部类Sync实现,然后Sync作为成员变量,实现各种方法,Sync我们会在下面说。先来了解一个Lock接口。
Lock:
public interface Lock {
//加锁,如果获取不到锁会到同步队列中阻塞
void lock();
//获取可中断锁
void lockInterruptibly() throws InterruptedException;
//尝试获取锁,成功返回true,否则返回false
boolean tryLock();
//带有超时时间tryLock
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解锁
void unlock();
//得到condition实例
Condition newCondition();
}
这些方法在ReentrantLock中实现,主要通过静态内部类Sync实现,也是Sync实现了AQS。。
Sync:
Sync并没有实现加锁,而是通过两个子类FairSync和NonfairSync实现,FairSync和NonfairSync对应着公平锁和非公平锁实现方式。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//加锁,抽象方法,子类实现
abstract void lock();
//尝试获取费公平锁,有两个地方调用,可自行通过Ctrl+Alt+H查看
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果当前没有现成持有锁
if (c == 0) {
//CAS将状态从0设置为参数值
if (compareAndSetState(0, acquires)) {
//将当前线程设置到AbstractOwnableSynchronizer中
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前锁被持有,当前线程为持有锁线程
else if (current == getExclusiveOwnerThread()) {
//ReentrantLock为可重入锁,可以对资源多次加锁
int nextc = c + acquires;
//超过integer的最大值,抛出异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//直接设置state,返回true
setState(nextc);
return true;
}
//如果持有锁的线程不是当前线程,返回false,获取锁失败
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;
}
//后面的方法比较简单,不就说了
...
}
tryRelease():
上面看了tryRelease代码,内容比较简单,state为0表示表示release成功,如果线程对资源加锁多次,那么解锁也需要相应的次数,才能release,然后被其他线程竞争这把锁。
PS:释放锁tryRelease是不区分共享锁和排他锁的。
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) {
//判断当前线程是否为同步队列head节点的后驱节点的线程,如果是可以获取锁,false阻塞等待,这是实现公平锁的关键,实现线程在队列的先入先出
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;
}
}
FairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//非公平锁加锁
final void lock() {
//CAS尝试将state从0设置为1,成功,设置持有锁线程,否则acquire获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//这里调用了前面Sync定义的nonfairTryAcquire()
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
总结:
对于加锁,解锁的流程,调用lock()unlock()的过程可以自己看下api,算是比较简单,也就不写代码了。ReentrantLock直接使用AQS整体实现非常简单,所以掌握AQS还是很有必要的,包括后面了解CountDownLatch、Semaphore实现都是很有必要的。