ReentrantLock 提供了两个构造函数,默认是非公平锁的构造函数,还有一个公平锁的构造函数。
可以参考: Java并发Concurrent包的锁(二)——自旋/阻塞/可重入
ReentrantLock 类中的公平和非公平,是通过对同步器 AbstractQueuedSynchronizer 的扩展加以实现的,也就是在tryAcquire的实现上做了语义的控制。
// 默认的非公平可重入锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 公平可重入锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
下面通过关键源码来分析:
非公平锁:
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
int c = getState();
// 如果当前状态c==0,说明是初始状态,将当前锁owner设置为当前线程,state加acquires的值一般为1
if (c == 0) {
// 尝试将state的值从0设置为acquires的值,一般为1
if (compareAndSetState(0, acquires)) {
// 将当前锁owner设置为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果锁状态已经被设置,而且锁的owner就是当前线程,那么就是可重入锁
else if (current == getExclusiveOwnerThread()) {
// 那么将之前的计数状态加上acquires的值
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 更新state的计数值
setState(nextc);
return true;
}
// 如果是其他线程控制的锁,返回false说明当前线程无法获得锁
return false;
}
公平锁:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果当前状态c==0,说明是初始状态,将当前锁owner设置为当前线程,state加acquires的值一般为1
if (c == 0) {
// 这个hasQueuedPredecessors下面分析
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 将当前锁owner设置为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果锁状态已经被设置,而且锁的owner就是当前线程,那么就是可重入锁
else if (current == getExclusiveOwnerThread()) {
// 那么将之前的计数状态加上acquires的值
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 更新state的计数值
setState(nextc);
return true;
}
// 如果是其他线程控制的锁,返回false说明当前线程无法获得锁
return false;
}
公平锁中有 hasQueuedPredecessors 方法,加入了当前线程(Node)之前是否有前置节点在等待的判断。
公平锁中有一个等待队列,遵循 FIFO 原则:
public final boolean hasQueuedPredecessors() {
// 尾节点
Node t = tail;
// 头节点
Node h = head;
Node s;
// 链表中有其他节点,并且头结点后第一个等待的线程是当前线程时,返回true
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
释放锁:
protected final boolean tryRelease(int releases) {
// 状态值减去releases值
int c = getState() - releases;
// 锁对象的owner必须是当前对象,当前对象才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 状态值为0
if (c == 0) {
// free置为true代表释放锁成功
free = true;
// 锁对象的owner置为null,锁完全释放
setExclusiveOwnerThread(null);
}
// 更新锁的状态值
setState(c);
return free;
}
公平锁在队列中取线程,保证先到先得,确保了公平性,但是吞吐量就没有非公平锁高了。非公平锁,由于可以在快速获取机制下获取下一个线程,保证进入和退出锁的吞吐量,但是队列中过早排队的线程会一直处于阻塞状态,造成“饥饿”场景。
在实际中,还要看线程临界区执行的时长大小,如果临界区时间比较长,那么相比之下公平锁带来的负担就相对小一些。