ReentrantLock中的公平锁和非公平锁的原理

ReentrantLock

ReentrantLock内部是通过AQS实现锁的功能,有公平锁和非公平锁两种实现。

  1. 公平锁,即锁的获取顺序按线程申请锁的先后顺序。
  2. 非公平锁,当一个线程t1申请锁时,锁刚好释放。即使已有其他线程在t1之前申请锁排队,线程t1还是会获取锁。这样减少了线程的等待唤醒的可能,减少上下文切换带来的开销。因为获取锁的顺序和申请顺序可能不一致所以叫非公平锁。

前置技能(先了解前置技能才好看懂)

  1. AQS
  2. CLH

ReentrantLock中的Sync

Sync是个抽象类,非公平锁和公平锁都基于这个类实现。这里实现了非公平的占用锁方法。非公平锁为了减少线程的等待唤醒。在锁释放的情况下,新的线程会直接占用锁而不管等待队列中有没有线程。这样减少了新线程的等待唤醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract static class Sync extends AbstractQueuedSynchronizer {
...
/**
*非公平锁的占用方法
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前的线程
final Thread current = Thread.currentThread();
//获取当前的锁占用状态
int c = getState();
//state==0则说明没有线程占用锁
if (c == 0) {
//此时会直接把锁给当前线程,而不去判断CLH队列中的是否已有等待线程。
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//这里是重入锁的处理,当前线程重入锁,state+1
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中的非公平锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;


final void lock() {
//当前线程进来先直接用cas尝试占用锁,失败再调用acquire
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

//acquire会调用tryAcquire判断占用锁是否成功,这里直接调用了Sync的非公平锁处理方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

公平锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

/**
*公平锁的处理
*
*/
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;
}
}

总结

  1. 非公平锁减少了线程发生等待唤醒的可能,节省了上下文切换的开销。
  2. 公平锁适合锁持有事件较长或者线程申请锁的间隔事件相对长的情况。
  3. 总的来所,公平锁的开销比非公平锁大,所以ReentrantLock默认支持的是非公平锁。