ReentrantLock与synchronized齐名的经典java锁,十分常用。
ReentrantLock特性:
- 可重入
- 可通过构造参数设置时公平锁还是非公平锁
- 需要明文释放锁,而synchronized是自动释放的
- 可响应中断
- 可在获取锁是设置超时时间
- 通知队列
非公平锁原理
ReentrantLock本身没有直接实现锁,核心逻辑都交由AQS框架完成,内部核心类Sync即直接继承自AbstractQueuedSynchronizer类,
非公平锁模式:
- 某个线程加锁时会先尝试直接设置同步状态,成功就退出
- 第一步尝试失败后调用AbstractQueuedSynchronizer的acquire方法,
- AbstractQueuedSynchronizer的acquire方法中先调用子类tryAcquire方法,当等待队列为空或者队首节点持有线程就是当前线程则cas设置同步状态,
- 第三步也没加锁成功就加入CLH队列
公平锁模式:
- 直接调用AbstractQueuedSynchronizer的acquire方法
- AbstractQueuedSynchronizer的acquire方法中先调用子类tryAcquire方法,发现锁没线程持有就直接尝试cas设置同步状态,若是发现持有线程就是当前线程则将累计信号量,
- 第二步没获取到锁就加入CLH队列
对比两种模式可发现非公平模式下,一旦锁空闲了下来,新到来的线程可能会抢在等待队列中线程前面拿到锁,形成“插队”拿锁,还是直接插在了所有线程前面,但等待队列中的线程没有插队的机会,抱怨也没用,仍然是老老实实的按照顺序排队。
ReentrantLock成员变量
private final Sync sync;
ReentrantLock内部只有一个成员变量Sync,作为一个核心内部类,绝大部分方法都直接依赖这个类实现
ReentrantLock内部类
ReentrantLock内部共3个内部类,其中Sync是一个抽象方法类,继承自AbstractQueuedSynchronizer,类NonFairSync与FairSync均继承自Sync
类Sync关键方法有3个 分别是lock、nonfairTryAcquire、tryRelease,其中lock是抽象方法,放下面介绍,先看nonfairTryAcquire
nonfairTryAcquire如期单词意义,是用来获取同步信号量的,并且是非公平式的,也就是说排队的线程获取顺序不是严格的先到先得,而是谁抢到就是谁的
//尝试获取释放状态
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;
//执行线程设置null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//非公平式尝试获取同步状态
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()) {
//锁已经被加锁过至少一次 并且加锁的是当前线程则信号量直接加acquires
int nextc = c + acquires;
//和小于0表明锁重入次数已经超过上限
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
NonfairSync类负责非公平锁 ,成员函数也很简单 仅有lock与tryAcquire两个,tryAcquire直接调用父类Sync的nonfairTryAcquire方法(这里吐槽下, nonfairTryAcquire感觉本来就应该放到NonfairSync类里面, 偏偏放Sync里面了,还绕了一个弯调用, 作者不知道咋想的 。。。)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//先快速尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 快速尝试失败后 再排队获取
acquire(1);
}
//尝试获取信号量
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSync跟NonfairSync一样也只有两个成员函数lock 与tryAcquire
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) {
//发现锁没人持有 并且等待队列是空的或者头结点线程就是当前线程 则cas尝试获取锁,
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;
}
}
ReentrantLock 构造函数
//默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//指定是哪种锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock
直接调用内部类的lock
public void lock() {
sync.lock();
}
unlock
直接调用内部类的unlock
public void unlock() {
sync.release(1);
}