ReentrantLock的公平锁与非公平锁使用详解(实现原理、性能对比与适用场景)

后端 潘老师 6天前 12 ℃ (0) 扫码查看

Java中ReentrantLock是一个非常重要的可重入锁工具,它支持公平锁和非公平锁两种模式。这两种锁模式在获取锁的机制、性能表现以及适用场景等方面都存在差异。下面,我们就来深入探讨一下它们的具体情况。

一、公平锁与非公平锁的基本概念

(一)公平锁

公平锁,从名字就能大概理解它的特性,即线程获取锁的顺序是按照请求锁的先后顺序来的。想象一下在银行排队办理业务,大家都按照取号的先后顺序等待,先到的人先办理,这就是公平的体现。在Java中,如果多个线程同时尝试获取公平锁,那么等待时间最长的那个线程会优先获得锁。这种方式最大的好处就是能避免线程饥饿的情况,每个线程最终都能有机会获取到锁。不过,为了实现这种公平性,它需要维护一个等待队列,这在一定程度上会降低系统的吞吐量。

(二)非公平锁

非公平锁则与公平锁不同,它在线程获取锁时是随机的,并不保证按照请求顺序来分配锁。这就好比在超市抢购商品,大家一哄而上,谁先抢到就是谁的,即使有些人早就站在旁边等待了。当一个线程释放锁后,其他线程会竞争这个锁,哪怕此时等待队列里已经有线程在等待了,新的线程也有可能直接获取到锁。这种方式虽然可能导致某些线程长时间得不到锁,也就是出现线程饥饿的现象,但它减少了线程上下文切换的开销,通常能提高系统的吞吐量。

二、公平锁与非公平锁的代码实现

ReentrantLock中,我们可以很方便地通过构造函数来指定锁的模式。以下是示例代码:

import java.util.concurrent.locks.ReentrantLock;

public class Example {
    // 默认情况下创建的是一个非公平锁
    private ReentrantLock nonFairLock = new ReentrantLock();

    // 通过传入true参数,创建一个公平锁
    private ReentrantLock fairLock = new ReentrantLock(true);

    public void testNonFairLock() {
        nonFairLock.lock();
        try {
            System.out.println("Non-fair lock acquired by " + Thread.currentThread().getName());
        } finally {
            nonFairLock.unlock();
        }
    }

    public void testFairLock() {
        fairLock.lock();
        try {
            System.out.println("Fair lock acquired by " + Thread.currentThread().getName());
        } finally {
            fairLock.unlock();
        }
    }
}

在上述代码中,nonFairLock是默认的非公平锁,而fairLock则是通过构造函数指定的公平锁。在testNonFairLocktestFairLock方法中,分别展示了这两种锁的使用方式。

三、公平锁的工作原理

(一)维护等待队列

公平锁内部有一个FIFO(先进先出)的等待队列。当一个线程尝试获取锁时,如果发现锁已经被其他线程占用了,它就会像排队一样,被添加到这个等待队列的末尾。这个队列就像是银行里大家排队的队伍,先到的线程排在前面,后到的线程排在后面。

(二)按顺序唤醒线程

当持有公平锁的线程释放锁时,公平锁会从等待队列的头部挑选一个线程,让它获取锁。这就如同银行叫号办理业务,按照排队顺序依次处理,保证了每个线程都能按照请求的先后顺序获得锁。

四、非公平锁的工作原理

(一)抢占式获取锁

非公平锁在获取锁时采用了一种“插队”的策略。当一个线程尝试获取锁时,即便已经有其他线程在等待队列中,它也会直接尝试获取锁。如果此时锁正好处于可用状态,那么这个线程就能立即获取到锁,而不需要像公平锁那样进入等待队列排队。

(二)减少上下文切换

由于非公平锁允许线程插队获取锁,减少了线程从等待状态到运行状态的上下文切换次数。这就好比在超市抢购时,不用等待排队,直接去抢商品,节省了等待的时间,从而提高了系统整体的吞吐量。

五、公平锁与非公平锁的性能对比

为了更直观地了解它们的差异,我们通过一个表格来对比一下:

特性 公平锁 非公平锁
线程获取顺序 按照请求顺序(FIFO) 随机,允许插队
吞吐量 较低(因维护等待队列) 较高(因减少上下文切换)
线程饥饿风险 较低(每个线程最终都会获得锁) 较高(某些线程可能长期等待)
适用场景 对公平性要求较高的场景 对性能要求较高的场景

六、公平锁与非公平锁的适用场景

(一)公平锁适用场景

在一些对公平性要求较高的场景中,公平锁就派上用场了。比如任务调度系统,需要确保所有任务都有机会执行,不能让某些任务一直得不到处理;还有在多个线程对共享资源的访问需要严格有序的情况下,也适合使用公平锁。

(二)非公平锁适用场景

在高并发场景下,如果更追求系统的吞吐量,非公平锁就是更好的选择。例如在一些电商促销活动中的抢购场景,对响应速度和吞吐量要求极高,而对线程执行顺序没有严格要求,此时非公平锁就能发挥它的优势。

七、公平锁与非公平锁的源码分析

接下来,我们通过分析ReentrantLock的部分源码,来进一步理解它们的区别。

(一)非公平锁的实现

非公平锁在尝试获取锁时,会直接调用CAS操作,尝试抢占锁。具体代码如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { // 锁未被占用
        if (compareAndSetState(0, acquires)) { // 尝试 CAS 获取锁
            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;
}

在这段代码中,当锁未被占用(c == 0)时,线程会直接尝试通过CAS操作获取锁。如果当前线程已经持有锁,且重入次数未超过最大限制,就更新重入次数并返回获取锁成功。

(二)公平锁的实现

公平锁在尝试获取锁时,会先检查等待队列中是否有其他线程在等待。代码如下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() && // 检查是否有其他线程在等待
            compareAndSetState(0, acquires)) { // 尝试 CAS 获取锁
            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()方法检查等待队列中是否有其他线程。只有在等待队列中没有其他线程等待时,才会尝试通过CAS操作获取锁。

八、总结

公平锁和非公平锁各有特点。公平锁严格按照线程请求锁的顺序分配锁,在对公平性要求较高的场景中表现出色,但由于维护等待队列,性能相对较低;非公平锁允许线程插队获取锁,虽然可能导致线程饥饿问题,但在高并发场景下能显著提高吞吐量。在实际的Java多线程编程中,我们需要根据具体的业务需求,合理选择使用公平锁或非公平锁,以达到最佳的性能和功能平衡。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/17523.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】