1、什么是公平锁与非公平锁
公平锁:公平锁就是保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁。
非公平锁:非公平锁则无法提供这个保障(先到的线程优先获取锁)。
2、ReentrantLock如何实现公平与非公平
Java并发包下面的ReentrantLock、ReadWriteLock默认都是非公平模式。
下面我们就来一起看看ReentrantLock是如何实现公平与非公平的。
ReentrantLock实现了Lock接口。提供了下面2个构造方法
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默认构造的是非公平锁NonfairSync。
NonfairSync和FairSync都是ReentrantLock的内部类,且继承ReentrantLock的内部抽象类Sync。
// 公平锁
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;
}
二者的主要去别在于是否有hasQueuedPredecessors方法,我们看下hasQueuedPredecessors的源码。该方法返回“队列中是否存在一个线程(先于当前线程)”,如果存在话,当前线程就要加入到队列的尾部。
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
*/
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;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
网上有这一张很棒的图:
结合这张图,二者的区别更明显。
公平锁就是在获取锁之前会先判断等待队列是否为空或者自己是否位于队列头部,该条件通过才能继续获取锁。
在结合兔子喝水的图分析,非公平锁获取所得顺序基本决定在9、10、11这三个事件发生的先后顺序,
- 1、若在释放锁的时候总是没有新的兔子来打扰,则非公平锁等于公平锁;
- 2、若释放锁的时候,正好一个兔子来喝水,而此时位于队列头的兔子还没有被唤醒(因为线程上下文切换是需要不少开销的),此时后来的兔子则优先获得锁,成功打破公平,成为非公平锁;
其实对于非公平锁,只要线程进入了等待队列,队列里面依然是FIFO的原则,跟公平锁的顺序是一样的。因为公平锁与非公平锁的release()部分代码是共用AQS的代码。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
* Wakes up node's successor, if one exists.
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
3、公平锁与非公平锁性能对比
非公平锁的效率高于公平锁:上文说到的线程切换的开销,其实就是非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。
沪漂程序员一枚。
坚持写博客,如果觉得还可以的话,给个小星星哦,你的支持就是我创作的动力。
推荐阅读:
Java内存模型-volatile的应用(实例讲解)
synchronized解决原子性-synchronized的三种应用方式(实例讲解)
线程池-一文弄懂Java里面的线程池ThreadPoolExecutor
可重入锁-面试题:synchronized是可重入锁吗
本文参考:一张图读懂非公平锁与公平锁