1、ReentrantLock的特性
ReentrantLock是Java并发包中提供的一个可重入的互斥锁。ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个Conditon。
可重入性:
是指可以支持一个线程对锁的重复获取与释放。原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞,并且lock和unlock的调用次数必须相等才会释放锁。
公平锁/非公平锁:
公平锁:是指线程获取锁的顺序和调用lock的顺序一样,FIFO(先进先出)方式获取和释放锁。
非公平锁:线程获取锁的顺序和调用lock的顺序无关,能否获取锁取决于调用时机。
synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。
2、ReentrantLock实现
ReentrantLock的所有锁相关操作都是通过Sync类实现,Sync继承于AbstractQueuedSynchronizer同步队列,并实现一些通用的接口实现。
NonfairSync继承于Sync,实现了非公平的方式获取锁;
FairSync继承于Sync,实现了公平的方式获取锁;
ReentrantLock类实现结构如下:
2.1、Sync实现
//sync继承于AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//抽象方法,需子类实现
abstract void lock();
//实现了非公平方式获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前锁状态,此状态c>0表示有线程获取到锁,重入的次数为c
int c = getState();
//无线程获取锁?
if (c == 0) {
//CAS方式获取锁,成功则设置获取独占锁的线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//获得锁的线程就是当前线程?则获取次数加1,并设置状态(即次数)
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;
}
//释放锁
protected final boolean tryRelease(int releases) {
//计算需要更新的状态值
int c = getState() - releases;
//若当前线程未获得锁,则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//若将要更新的状态值为0,表示当前线程最后一次释放锁;
//此时锁才会真正的释放,其他线程才能获取;
//否则表示当前线程获取锁的次数大于释放锁的次数
if (c == 0) {
free = true;
//清除记录的独占锁的线程
setExclusiveOwnerThread(null);
}
//更新状态
setState(c);
return free;
}
//判断当前线程释放获取独占锁
protected final boolean isHeldExclusively() {
//当前线程为记录的获取独占锁的线程,则表示当前线程获得独占锁
return getExclusiveOwnerThread() == Thread.currentThread();
}
//创建新的条件队列
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取获取独占锁的线程对象
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取当前线程获取锁的次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//判断锁是否被占用
final boolean isLocked() {
return getState() != 0;
}
}
2.2、非公平锁NonfairSync的实现
//非公平锁NonfairSync的实现,其继承于Sync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//lock接口实现;
//首先通过CAS试图获取锁,获取成功则设置锁的Owner;
//否则调用acquire获取锁,acquire又或调用tryAcquire获取锁,
//而tryAcquire是通过非公平的方式获取锁。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//非公平方式获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
2.3、公平锁FairSync的实现
//公平锁FairSync 的实现,其继承于Sync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//lock接口实现,自己调用acquire获取锁;
//acquire又会调用tryAcquire获取锁,而tryAcquire是通过公平(FIFO)
//的方式获取锁。
final void lock() {
acquire(1);
}
//公平的方式获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前锁状态,此状态c>0表示有线程获取到锁,重入的次数为c
int c = getState();
//无线程获取锁?
if (c == 0) {
//当前节点无前驱节点并且当前线程CAS更新状态成功;、
//表示当前线程公平的获取到锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//获得锁的线程就是当前线程?则获取次数加1,并设置状态(即次数)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
2.4、锁可重入的实现原理
通过以上源码分析可知,当某个节点获取到锁时,会通过setExclusiveOwnerThread()方法记录获取独占锁的线程Thread;当某个线程获取锁时,当锁已被占用,会判断占用锁的线程是否为当前线程;是则直接更新锁状态,表示获取到锁;否则获取锁失败。
2.5、锁公平与非公平的实现原理
公平锁是通过FairSync实现的,其在tryAcquire获取锁时,会判断同步队列中当前节点是否有前驱节点;有前驱节点,则获取锁失败,进入同步队列,等待获取锁;无前驱节点时,表示当前节点是同步队列中等待锁时间最长的节点,则当前节点优先获取锁资源。
非公平锁是通过NonfairSync实现的,其在lock及tryAcquire时,会先通过CAS的方式尝试获取锁,获取失败才会进入同步队列等待。这就导致当某个线程刚释放锁,而同步队列中被unpark的头节点还未CAS获取到锁的时间间隙,当前线程先于同步队列头结点通过CAS获取锁。使得某些线程会等待很长时间才会获得锁,这是非公平性的。
ReentrantLock的构造方法如下:
//无参构造方法,会通过非公平的方式实现锁
public ReentrantLock() {
sync = new NonfairSync();
}
//带参数的构造方法;
//fire=true:会通过公平的方式构造锁;
//fire=false:会通过非公平的方式构造锁;
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}