ReentrantLock
从之前的环节我们了解到,synchronized和ReentrantLock都是可重入锁,但是ReentrantLock不像synchronized那样是隐式的重入锁,而是在调用lock()方法时,不阻塞已经获取到锁的线程。ReentrantLock为我们实现了公平锁和非公平锁,同时支持重入。
ReentrantLock的重入性
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题:
- 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则在此成功获取。
- 锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
ReentrantLock是通过组合自定义同步器来实现锁的获取与释放,以非公平锁(默认)实现为例:
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 非公平获取锁(ReentrantLock的内部类Sync中)
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;
}
该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态进行增加并返回true,表示获取同步状态成功。
接下来我们一起来看一下ReentrantLock是如何释放同步状态的:
protected final boolean tryRelease(int releases) {
// 状态-
int c = getState() - releases;
// 当前线程非锁持有者
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 定义释放标志
boolean free = false;
if (c == 0) {// 状态为0代表锁释放
free = true;
setExclusiveOwnerThread(null);// 初始化锁持有者
}
setState(c);// 设置线程状态
return free;
}
如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了(state=0),才能返回true。在tryRelease()方法中可以看到,该方法将同步状态是否为0作为最终释放条件,当同步状态为0时,将占有线程设为null,并返回true,表示释放成功。
ReentrantLock的公平性
公平锁和非公平锁的区别
公平性与否是准对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO(先进先出)。对于非公平锁,只要CAS设置同步状态成功(如上面nonfairTryAcquire()方法所示),则表示当前线程获取了锁,而公平锁不同,如下所示:
// 公平方式获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 唯一不同的位置:!hasQueuedPredecessors()
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;
}
该方法与nonfairTryAcquire(inti acquires)比较,唯一不同的位置为判断条件中多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
// AQS中的hasQueuedPredecessors()方法
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁和非公平锁的区别:
- 公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平锁只要获取了同步状态就能成功获取锁
- 公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换,非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换保证了其更大的吞吐量