ReentrantLock是重入锁
重入:表示能够对共享资源重复加锁,即当前线程再次获取锁时不会被阻塞。
1. 那重入锁是如何实现的呢?
如果该同步状态不为0,表示此时同步状态已被线程获取。再判断持有同步状态的线程是否是当前线程,如果是,同步状态再次+1并返回true,表示持有线程重入同步块。
释放过程:
当且仅当同步状态减为0并且持有线程为当前线程时表示锁被正确释放。
否则调用setState()将减1后的状态设置回去。
ReentrantLock提供了两种锁机制:公平锁、非公平锁
2. 公平锁与非公平锁
公平锁:锁的获取顺序符合时间上的顺序,即等待时间最长的线程最先获取锁。
public ReentrantLock() {
sync = new NonfairSync();
}
我们提供源码可以发现ReentrantLock无参构造使用的是非公平锁。
要使用公平锁,就调用ReentrantLock有参构造传入ture,获取内置的公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的实现
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
它里边最重要的就是这段代码:
//获取同步状态之前先判断下当前节点是否有前驱节点,如果有,获取失败
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
直到它前边没有节点,才让这个节点获得锁,这就保证了等待时间最长的最先获取到锁(因为是尾插)。
那为什么ReentrantLock默认采用的是非公平锁,因为非公平锁的效率比公平锁高100倍以上,这是因为非公平锁省略了上下文切换等操作。
公平锁与非公平锁的对比:
公平锁保证了获取到锁的线程一定是等待时间最长的线程,保证了请求资源时间上的绝对顺序,需要频繁的进行上下文切换,性能开销较大。
非公平锁保证系统有更大的吞吐量(效率较高),但是会造成线程“饥饿现象”(有的线程可能永远无法获得锁)