锁按照公平性划分为公平锁和非公平锁,在Java中,ReentrantLock有这两种锁的具体实现,下文进行展示。
说明
以食堂打饭的场景举例说明
1. 公平锁
想要获取锁资源的线程在队列里进行排队等待,新来的线程去队尾排队,锁资源释放的时候只有队首线程可以获得锁资源。
- 排队等待
image.png
2. 非公平锁
新来的线程直接和队首线程争抢锁资源,如果争抢到了,则直接获取锁资源,队首线程继续等待。 如果新来的线程竞争失败,则去队尾进行排队,只能等待队列前所有线程执行完毕后自己才能获取锁。
注意: 锁的非公平性只在首次和队首线程进行锁竞争时有体现,竞争失败入列后则与公平锁执行方式一致
- 插队成功
image.png
- 插队失败
image.png
重点代码
1. 初始化默认为非公平锁,也可以设置为公平锁
代码语言:javascript复制public ReentrantLock() {
// 默认为非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 可以设置为公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
2. 入列之前lock方法中尝试插队获取锁
- 公平锁排队获取锁
static final class FairSync extends Sync {
final void lock() {
// 排队获取锁,如果没获取到就排队
acquire(1);
}
}
- 非公平锁入列之前
首次
尝试获取锁
static final class NonfairSync extends Sync {
final void lock() {
// 插队尝试获取锁(第一次尝试)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 排队获取锁,如果没获取到就排队
acquire(1);
}
}
3. tryAcquire时第二次尝试插队获取锁资源
- 公平锁
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors() 表示前面是队列中是否有线程在排队(true-有、false-没有)
// 这段的逻辑是队列中没有线程排队了,才能获取锁,如果前面有线程排队,即使锁资源刚好释放,
// 队首线程没有及时获取到锁,被当前线程获取到了,也不允许当前线程拥有锁
// 公平锁就要保证新来的线程始终到队尾排队
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;
}
4. 两次都没有插队获取到所资源,则入列变成公平获取锁的一员
代码语言:javascript复制public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// acquireQueued() 入列等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
意义
ReentrantLock默认使用非公平锁的意义是:
非公平锁减少了线程切换带来的上下文切换,牺牲了公平性但是提高了性能。 提供公平锁是对锁的获取顺序进行了保证,牺牲了部分性能。
参考
- 看完你就明白的锁系列之锁的公平性