前言
在Java 1.5版本的时候,Java提供了新的同步机制—Lock接口,通过Lock接口可以实现不同类型的锁来支持多线程的同步。相比较synchronized关键字,通过Lock实现的锁可以更好的控制同步块的粒度。 在Lock子类的内部实现中,锁是通过Java的队列同步器实现的(AbstractQueuedSynchronizer),不熟悉队列同步器的同学,可以参考我的另一篇博客[Java并发编程(三)—队列同步器(AbstractQueuedSynchronizer)](https://blog.youkuaiyun.com/programerxiaoer/article/details/81463219)关于ReentrantLock的原理分析
ReentrantLock是Lock接口的一个子类,它是一种可重入锁,也就表示持有该锁的线程可以对资源重复加锁。除此之外,可重入锁也支持公平锁和非公平锁。ReentrantLock的可重入特性
可重入是指的线程在获取锁之后能够再次获取到该锁并且不会阻塞。我们都知道在队列同步器通过模板方法tryAcquire获取锁,并且通过一个int类型的状态标志来标识锁是否获取成功,而实现可重入的特性就需要依赖状态标志。final Thread current = Thread.currentThread();
int c = getState(); //状态标志
if (c == 0) { //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;
以上就是ReentrantLock的可重入特性的原理实现。通过状态标志,当状态标志为0时,表示当前线程还未获取锁,然后尝试获取锁。如果是当前线程在此获取锁时,更新状态标志。
我们可以看到只有状态标志为0时,线程才能尝试获取锁,那么可重入锁在获取锁n次后,相应的也需要释放n次所。
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;
}
ReentrantLock的公平锁与非公平锁
ReentrantLock还分为公平锁和非公平锁。其中公平锁就是线程获取锁的顺序遵循FIFO,也就是在等待队列中,线程安装先进先出的规则获取锁。而非公平锁只要队列中的线程能够设置同步状态成功就可以获取锁,也就是线程获取锁时根据CPU调度的。所以非公平锁可能导致线程“饥饿”问题,是的其他线程能够得到的时间片较少。但是使用非公平锁时,线程的切换次数要少于使用公平锁,所以效率也相应较高。公平锁的实现
ReentrantLock中公平锁也是通过队列同步器提供的模板方法实现锁机制。static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
@ReservedStackAccess
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;
}
}
非公平锁的实现
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//可以看到非公平锁与公平锁的差别只是没有了hasQueuedPredecessors()的判断条件
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;
}
从上面的代码可以看出,在代码实现上公平锁与非公平锁的差别只是判断了尝试获取锁的线程在队列中是否有前置节点。如果有的话,在公平锁的情况下就不能获取锁,非公平锁的情况下可以获取锁。