基本介绍
ReentrantLock,可重入锁,基于AQS实现的互斥锁,在互斥锁之上支持可重入。可重入的意思是,同一个线程可以多次调用lock方法,而不会导致自己等待自己锁的释放。根据内部实现,分为公平性可重入锁和非公平性可重入锁。由构造函数来指定其公平性,默认使用非公平性实现。ReentrantLock实现了Lock接口,实现的接口如下:
public interface Lock {
// 尝试获取锁,获取不到则阻塞等待,不响应中断
void lock();
// 尝试获取锁,获取不到则阻塞等待,响应中断
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,立即返回,获取成功返回true,失败则返回false
boolean tryLock();
// 超时获取锁,获取到则返回,获取不到知道超时时间过,返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁,需要在持有锁的线程中调用,否则会抛IllegalMonitorStateException,一般放到finally块中执行,确保在碰到任何异常,都正确能释放锁
void unlock();
// 新建一个同步等待条件
Condition newCondition();
}
示例使用
public class Counter {
private volatile int value = 0;
private Lock lock = new ReentrantLock();
public int inc() {
lock.lock();
try {
++value;
} finllay {
lock.unlock();
}
}
}
上面的例子仅作使用示例使用,在开发中常规的多线程并发计数器直接用基于CAS实现的atomic类即可(AtomicInteger,AtomicLong)
Lock vs Synchronized
synchronized的实现在底层,它的使用比较简单,基于方法或者基于方法块定义同步,可基于对象,class类来进行锁定;但synchronized无法实现超时锁,也不支持中断。而JUC中Lock的实现,可以实现超时获取锁(获取成功立刻返回,获取失败,根据超时时间返回),tryLock尝试获取锁(无论获取成功或者失败,都立马返回),锁等待过程支持中断。
源码分析
ReentrantLock实现了Lock接口,通过内部类Sync实现AQS提供的独占式获取锁方法和释放锁方法,来实现独占锁的功能,同时还区分公平性实现和非公平性实现:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
// 非公平可重入锁实现
static final class NonfairSync extends Sync {
...
}
// 公平性可重入锁实现
static final class FairSync extends Sync {
}
...
}
前面说到,ReentrantLock分为公平性可重入锁和非公平性可重入锁,通过其构造函数来指定公平性,默认使用的是非公平性实现。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
锁的公平性与非公平性获取
非公平获取体现在两个地方:
1)在NonfairSync
中的lock方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2)在Sync
的nonfairTryAcquire方法,这是由上面的lock方法中的acquire方法衍生调用的:
Tip: acquire会调用tryAcquire方法,而NonfairSync的tryAcquire方法里面又调用了Sync中的的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) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这里1)和2)的非公平性是说,任何线程,进入这段代码,先尝试CAS抢占锁(对AQS来说,指抢占同步状态state),而不用去理会AQS同步队列中是否有其他线程先于这些线程在等待获取独占锁。
基于上面的描述,我们就可以想象公平性的实现,实际上就是要先判断AQS同步队列是否还有其他线程在等待,FIFO的思想,先到的先尝试获取锁,这不就是公平的么,看代码,在FairSync
类的tryAcquire方法中:
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;
}
注意到上面,当同步状态为0时候,优先判断是否同步队列里面有线程在等待,若有则当前线程不能抢占修改同步状态,必须失败(返回false),然后让AQS将它加到同步队列尾部。
锁的释放
公平性和非公平性的释放保持一致,都在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;
}
支持中断的锁获取
直接调用AQS提供的acquireInterruptibly方法,而该方法会调用tryAcquire方法,实现由上面所述,分公平性和非公平性:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
超时获取锁
直接调用AQS提供的tryAcquireNanos方法,该方法会调用tryAcquire方法,实现由上面所述,分公平性和非公平性:
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
tryLock使用非公平实现
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
newCondition
通过newCondition()方法来构造一个Condition,一个Condition代表一个条件,当获取锁的情况下,可使用Condition来实现类似Object的wait/notify机制
eg:
private Lock lock = new ReentrantLock();
private Condition cond = lock.newCondition();
public void thread1() {
lock.lock();
try {
while (!conditionMatch()) {
cond.await();
}
} finally {
lock.unlock();
}
}
public void thread2() {
lock.lock();
try {
cond.signalAll();
} finally {
lock.unlock();
}
}