公平锁
公平锁的主要函数调用轨迹:
-
ReentrantLock类:lock()
-
ReentrantLock类的静态内部类Sync(继承自AbstractQueuedSynchronizer类):acquire(int arg)
- FairSync类:tryAcquire(int acquires)
非公平锁
公平锁的主要函数调用轨迹:
-
ReentrantLock类:lock()
-
ReentrantLock类的静态内部类Sync(继承自AbstractQueuedSynchronizer类):acquire(int arg)
- Sync类:nonfairTryAcquire(int acquires)
从上面的主要函数调用轨迹可以看到,公平锁与非公平锁的区别仅在第三点,看JDK源码也可以发现,事实确实如此。
主要代码分析
/***************************公平与非公平的公共部分***********************************/
//Sync有两种实现方式:公平、非公平
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//1、先尝试获取锁,若成功则函数直接结束,即tryAcquire返回true
//2、若获取失败,则将当前线程构造成节点,并调用addWaiter方法将节点放到AQS尾部
//3、调用acquireQueued方法不断循环获取同步状态
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!
//注意这个函数,它对与理解下面结论十分重要
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
//当且仅当 当前节点的前驱节点为头结点时才会调用tryAcquire方法获取同步状态
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
if (interrupted)
selfInterrupt();
throw t;
}
}
/***************************公平与非公平的不同之处**********************************/
//先看公平方式
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.
*/
//重写了tryAcquire方法
@ReservedStackAccess
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;
}
}
//附上hasQueuedPredecessors方法
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//当队列为空或者队列不为空但head节点的下一节点就是当前线程时返回false
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
//非公平方式
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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;
}
下面仅个人理解,欢迎指正:
上面的代码中,ReentrantLock类的静态内部类Sync继承自AbstractQueuedSynchronizer类,在Sync类中仅对AbstractQueuedSynchronizer类中的TryAcquire方法分别进行了公平性实现(TryAcquire)和非公平性实现(nonFairTryAcquire),因此获取锁的过程中所涉及的其他函数均直接调用了其父类的函数。在这样的前提下,通过上述代码可以发现:
公平锁和非公平锁中的“公平性”仅体现在首次获取锁上(不包含重入情况),原因见下:
- 线程首次获取锁时,调用了TryAcquire方法,在这里公平锁要判断是否存在前驱节点,而非公平锁不需要判断,因此公平锁遵循“先来后到”,非公平锁不需要;
- 若首次没有成功获取到锁,那么无论是公平锁还是非公平锁都要被放入同步队列中,根据队列的FIFO特点以及acquireQueued逻辑(只有前驱节点为头结点的线程节点才能够获取锁)来看,无论是公平锁还是非公平锁都只能根据进入队列的先后顺序出队列。也就是说,在同步队列中时,非公平锁也得遵守“先来后到”。不过,公平锁在同步队列不为空的情况下会直接进入同步队列,而非公平锁会先尝试获取锁,失败之后才会进入同步队列。
Lock方法和TryLock方法的区别:
如上面所说,Lock方法会调用AQS的acquire方法,也就意味着若当前线程在获取锁失败的时候会进入同步队列(阻塞状态)。下面我们来看TryLock方法源码:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
很明显TryLock直接调用了nonfairTryAcquire方法,也就是说当前线程在获取锁失败的时候会直接返回false而不会进入阻塞态。
也正是因为TryLock的存在使得ReentrantLock实现非阻塞获取锁(区别于Sychronized)。