在JAVA并发中,实现锁通常有synchronized关键字和Lock两种方式,以前不了解Lock锁的时候,感觉Lock锁用起来太复杂,需要手动的加锁和解锁,如果忘记释放又会产生锁死。相比较而言synchronized隐式的用起来就非常简单,但是看了源码以后就被它深深的吸引了。
ReentrantLock 是 Lock 的实现类,提供了两个构造器,里面有一个内部类Sync,Sync继承AQS,添加锁和释放锁的大部分操作实际上都是在Sync中实现的。构造器默认初始化的是非公平锁来提高并发度。篇幅有限,我贴出来非公平锁代码做说明。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state变量值
int c = getState();
if (c == 0) { //没有线程占用锁,锁处于空闲状态,使用CAS获取
//没有 !hasQueuedPredecessors() 判断,不考虑自己是不是在队头,直接申请锁
if (compareAndSetState(0, acquires)) {
//占用锁成功,当前线程就是锁的持有者 setExclusiveOwnerThread(current);
return true;
}
}
//获取失败则先判断当前线程是否是锁的持有者,也是ReentrantLock实现可重入的原因
else if (current == getExclusiveOwnerThread()) { //当前线程已经占用该锁
int nextc = c + acquires;
//可以看出源码设计者的周到,考虑到了锁溢出的情况
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); //将当前线程持有的锁+1
return true;
}
return false;
}
}
多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁(所以非公平锁有可能出现后申请锁的线程先获取锁的场景)
通过源码看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()判断当前节点在等待队列中是否有前驱节点,如果有,则说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败;如果当前节点没有前驱节点,才有做后面的逻辑判断的必要性。
现在了解了它的运行机制,发现在多线程多并发的环境下,使用ReentrantLock在性能上有更加出色的表现,对于设计者在两种锁获取资源方法的周到考虑也是自己值得学习的方向。