行业资讯 2025年08月6日
0 收藏 0 点赞 217 浏览 3559 个字
摘要 :

文章目录 一、公平锁与非公平锁的基本概念 (一)公平锁 (二)非公平锁 二、公平锁与非公平锁的代码实现 三、公平锁的工作原理 (一)维护等待队列 (二)按顺序唤……




  • 一、公平与非公平锁的基本概念
    • (一)公平锁
    • (二)非公平锁
  • 二、公平锁与非公平锁的代码实现
  • 三、公平锁的工作原理
    • (一)维护等待队列
    • (二)按顺序唤醒线程
  • 四、非公平锁的工作原理
    • (一)抢占式获取锁
    • (二)减少上下文切换
  • 五、公平锁与非公平锁的性能对比
  • 六、公平锁与非公平锁的适用场景
    • (一)公平锁适用场景
    • (二)非公平锁适用场景
  • 七、公平锁与非公平锁的源码分析
    • (一)非公平锁的实现
    • (二)公平锁的实现
  • 八、总结

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.zuozi.net/10522.html

管理员

相关推荐
2025-08-06

文章目录 一、Reader 接口概述 1.1 什么是 Reader 接口? 1.2 Reader 与 InputStream 的区别 1.3 …

986
2025-08-06

文章目录 一、事件溯源 (一)核心概念 (二)Kafka与Golang的优势 (三)完整代码实现 二、命令…

463
2025-08-06

文章目录 一、证明GC期间执行native函数的线程仍在运行 二、native线程操作Java对象的影响及处理方…

347
2025-08-06

文章目录 一、事务基础概念 二、MyBatis事务管理机制 (一)JDBC原生事务管理(JdbcTransaction)…

455
2025-08-06

文章目录 一、SnowFlake算法核心原理 二、SnowFlake算法工作流程详解 三、SnowFlake算法的Java代码…

516
2025-08-06

文章目录 一、本地Jar包的加载操作 二、本地Class的加载方法 三、远程Jar包的加载方式 你知道Groo…

831
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号