Java ReentrantLock锁的公平性与非公平性

锁按照公平性划分为公平锁非公平锁,在Java中,ReentrantLock有这两种锁的具体实现,下文进行展示。

说明

以食堂打饭的场景举例说明

1. 公平锁

想要获取锁资源的线程在队列里进行排队等待,新来的线程去队尾排队,锁资源释放的时候只有队首线程可以获得锁资源。

  • 排队等待
Java ReentrantLock锁的公平性与非公平性_第1张图片
image.png
2. 非公平锁

新来的线程直接和队首线程争抢锁资源,如果争抢到了,则直接获取锁资源,队首线程继续等待。
如果新来的线程竞争失败,则去队尾进行排队,只能等待队列前所有线程执行完毕后自己才能获取锁。

注意: 锁的非公平性只在首次和队首线程进行锁竞争时有体现,竞争失败入列后则与公平锁执行方式一致

  • 插队成功
Java ReentrantLock锁的公平性与非公平性_第2张图片
image.png
  • 插队失败
Java ReentrantLock锁的公平性与非公平性_第3张图片
image.png

重点代码

1. 初始化默认为非公平锁,也可以设置为公平锁
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. 两次都没有插队获取到所资源,则入列变成公平获取锁的一员
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // acquireQueued() 入列等待
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

意义

ReentrantLock默认使用非公平锁的意义是:

非公平锁减少了线程上下文切换,牺牲了公平性但是提高了性能。
提供公平锁是对锁的获取顺序进行了保证,牺牲了部分性能。

参考

  1. 看完你就明白的锁系列之锁的公平性

你可能感兴趣的:(Java ReentrantLock锁的公平性与非公平性)