重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。
每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。
从类图可以看到, ReentrantLock 最终还是使用AQS 来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
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类直接继承自AQS , 它的子类NonfairSync和FairSync 分别实现了获取锁的非公平与公平策略。
在这里, AQS 的state 状态值表示线程获取该锁的可重入次数, 在默认情况下, state的值为0 表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时会尝试使用CAS设置state 的值为1,如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置为2, 这就是可重入次数。在该线程释放该锁时,会尝试使用CAS 让状态值减1,如果减1后状态值为0,则当前线程释放该锁。
当一个线程调用该方法时,说明该线程希望获取该锁。如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程, 并设置AQS 的状态值为1 ,然后直接返回。如果当前线程之前己经获取过该锁,则这次只是简单地把AQS 的状态值加1 后返回。如果该锁己经被其他线程持有,则调用该方法的线程会被放入AQS 队列后阻塞挂起。
public void lock() {
sync.lock();
}
在如上代码中, ReentrantLock 的lock( )委托给了sync 类,根据创建ReentrantLock构造函数选择sync的实现是NonfairSync 还是FairSync ,这个锁是一个非公平锁或者公平锁。
final void lock() {
// cas 设置状态值
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//调用 AQS的acquire 方法
acquire(1);
}
在代码(1)中,因为默认AQS 的状态值为0,所以第一个调用Lock 的线程会通过CAS 设置状态值为1, CAS 成功则表示当前线程获取到了锁, 然后setExclusiveOwnerThread 设置该锁持有者是当前线程。
如果这时候有其他线程调用lock 方法企图获取该锁,CAS会失败,然后会调用AQS的acquire方法。注意,传递参数为1 ,这里再贴下AQS 的acquire 的核心代码。
public final void acquire(int arg) {
//(3) 调用ReentrantLock重写的tryAcquire方法
if (!tryAcquire(arg) &&
// tryAcquiref返回false会把当前线程放入AQS 阻塞队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
之前说过,AQS 并没有提供可用的tryAcquire 方法, tryAcquire 方法需要子类自己定制化,所以这里代码(3)会调用ReentrantLock 重写的t可Acquire 方法。我们先看下非公平锁的代码。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// (4)当前AQS状态值为0
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//(5)当前线程是该锁持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//(6)
return false;
}
重点看公平锁的FairSync 重写的tryAcquire 方法。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// (7)当前AQS状态值为0
if (c == 0) {
//(8) 公平性策略
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//(9)当前线程是该锁持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//(10)
return false;
}
该方法与lock( )方法类似,它的不同在于,它对中断进行响应,就是当前线程在调用该方法时,如果其他线程调用了当前线程的interrupt( )方法, 则当前线程会抛出InterruptedException异常, 然后返回。
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//中断抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
// 调用AQS 可被中断的方法
doAcquireInterruptibly(arg);
}
尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁井返回true,否则返回false 。注意,该方法不会引起当前线程阻塞。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
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;
}
尝试获取锁,与tryLock( )的不同之处在于,它设置了超时时间,如果超时时间到没有获取到该锁则返回false 。
尝试释放锁,如果当前线程持有该锁, 则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0 ,则当前线程会释放该锁, 否则仅仅减1而己。如果当前线程没有持有该锁而调用了该方法则会抛出illegalMonitorStateException 异常,代码如下。
public void unlock() {
sync.release(1);
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//如果不是锁持有者调用UNlock抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//)如采当前可重入次数为0,则清空锁持有线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置可重入次数
setState(c);
return free;
}