公平锁与非公平锁的区别及其在 ReentrantLock 中的实现详解

引言

在并发编程中,锁是用于解决多个线程对共享资源进行竞争访问的常见工具。锁机制的设计可以分为多种形式,其中最为常见的是公平锁非公平锁。Java 提供了 ReentrantLock 来实现这两种锁的机制,通过使用公平和非公平锁,我们可以对线程竞争进行不同的控制。

公平锁保证锁的获取按照线程请求的顺序进行,而非公平锁则允许线程抢占锁。虽然公平锁可以避免线程饥饿,但它也带来了性能上的开销。本文将详细讲解公平锁与非公平锁的区别,探讨公平锁的缺点,并通过代码和图文解释 ReentrantLock 是如何实现这两种锁的。


第一部分:什么是公平锁与非公平锁

1.1 公平锁

公平锁是指线程获取锁的顺序严格按照它们请求锁的顺序进行。即每个线程获取锁的顺序与它们排队的顺序保持一致,类似于在银行排队取号的机制。公平锁的实现通过一个先进先出的队列(FIFO)来管理线程,当某个线程持有锁时,其他线程会进入等待队列,直到轮到自己。

特点

  • 保证每个线程都能够公平地获得锁。
  • 避免了线程饥饿问题。

示意图:公平锁的锁获取流程

+-------------------+
|  线程1 请求锁      | ---> 进入等待队列
+-------------------+
+-------------------+
|  线程2 请求锁      | ---> 进入等待队列
+-------------------+
+-------------------+
|  线程3 请求锁      | ---> 进入等待队列
+-------------------+
1.2 非公平锁

非公平锁则是一种允许线程“抢占”锁的机制。当某个线程请求锁时,它可以尝试直接获取锁,而不必等待已经在等待队列中的线程。这意味着如果当前锁没有被其他线程持有,那么请求锁的线程可以直接获取到锁,而不用排队。

特点

  • 可能导致线程饥饿:某些线程可能长时间获取不到锁。
  • 性能较高,因为非公平锁减少了上下文切换和线程调度的开销。

示意图:非公平锁的锁获取流程

+-------------------+
|  线程1 请求锁      | ---> 立即尝试获取锁
+-------------------+
+-------------------+
|  线程2 请求锁      | ---> 立即尝试获取锁
+-------------------+
+-------------------+
|  线程3 请求锁      | ---> 立即尝试获取锁
+-------------------+

第二部分:公平锁的优缺点

2.1 公平锁的优点
  1. 避免线程饥饿:公平锁严格按照请求的顺序分配锁,保证每个线程都有机会获取锁,防止某些线程长时间等待,特别是在高并发场景中,这种公平机制非常重要。

  2. 任务的公平调度:在某些业务逻辑中,要求任务按照请求的顺序来处理,因此使用公平锁可以确保任务的顺序性,防止一些任务提前或被长时间延迟。

2.2 公平锁的缺点
  1. 性能开销较大:公平锁需要维护一个排队机制,每当有新线程请求锁时,系统需要检查等待队列中的线程并按照顺序分配锁,这增加了锁的获取成本。

  2. 上下文切换较频繁:由于每个线程都需要按照顺序等待获取锁,频繁的线程调度和上下文切换可能会降低系统的性能,特别是在高并发的场景中。

  3. 降低吞吐量:由于公平锁严格按照顺序分配锁,某些本可以快速执行的线程也需要等待队列中其他线程先获取锁,这可能导致系统吞吐量的降低。

示意图:公平锁中的上下文切换

+--------------------+               +--------------------+
| 线程1 获取锁         |  --> 切换到   | 线程2 等待获取锁     |
+--------------------+               +--------------------+
+--------------------+               +--------------------+
| 线程2 获取锁         |  --> 切换到   | 线程3 等待获取锁     |
+--------------------+               +--------------------+

第三部分:非公平锁的优缺点

3.1 非公平锁的优点
  1. 高性能:非公平锁允许线程“抢占”锁,而不必按照排队的顺序等待。这样可以减少线程的上下文切换和调度开销,从而提升系统的并发性能。

  2. 高吞吐量:由于锁可以被直接抢占,非公平锁在某些情况下能够快速处理短时间的任务,提高系统的整体吞吐量。

3.2 非公平锁的缺点
  1. 可能导致线程饥饿:非公平锁不保证线程获取锁的顺序,某些线程可能长期处于等待状态,特别是在高并发场景中,如果线程抢不到锁,就会出现线程饥饿问题。

  2. 任务顺序不确定:非公平锁不保证任务的执行顺序,在某些对顺序要求严格的场景下,非公平锁可能不合适。


第四部分:ReentrantLock 的公平锁和非公平锁实现

4.1 ReentrantLock 概述

ReentrantLock 是 Java 提供的显式锁,它比 synchronized 具有更丰富的功能。ReentrantLock 允许线程重复获取锁,并提供了公平锁与非公平锁的选择。

  • 公平锁ReentrantLock 的构造方法可以通过传入 true 参数来创建公平锁。
  • 非公平锁ReentrantLock 的默认构造方法使用非公平锁,也可以通过传入 false 参数来创建非公平锁。
4.2 ReentrantLock 的公平锁实现

当使用公平锁时,ReentrantLock 通过内部维护一个FIFO队列来确保每个线程按照请求顺序获取锁。具体实现上,ReentrantLock 使用 AbstractQueuedSynchronizer(AQS)的 tryAcquire 方法,检查当前是否有其他线程在排队,确保锁按照顺序分配。

代码示例:ReentrantLock 公平锁

import java.util.concurrent.locks.ReentrantLock;public class FairLockExample {private static final ReentrantLock lock = new ReentrantLock(true);  // 公平锁public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(new Task(), "线程-" + i).start();}}static class Task implements Runnable {@Overridepublic void run() {lock.lock();try {System.out.println(Thread.currentThread().getName() + " 获取了锁");Thread.sleep(1000);  // 模拟业务操作} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
}

运行结果(不同线程获取锁的顺序依次进行):

线程-0 获取了锁
线程-1 获取了锁
线程-2 获取了锁
线程-3 获取了锁
线程-4 获取了锁

实现细节

  1. 当线程尝试获取锁时,ReentrantLock 会检查是否有其他线程在等待队列中。如果有,当前线程会被加入到等待队列的末尾。
  2. 锁的释放时,系统会唤醒等待队列中的第一个线程,从而保证锁的获取顺序是公平的。
4.3 ReentrantLock 的非公平锁实现

非公平锁是 ReentrantLock 的默认锁实现。在非公平锁中,线程在请求锁时,不会关心等待队列中的顺序,而是直接尝试获取锁。这种抢占式的策略减少了排队和上下文切换,提高了系统的性能。

代码示例:ReentrantLock 非公平锁

import java.util.concurrent.locks.ReentrantLock;public class UnfairLockExample {private static final ReentrantLock lock = new ReentrantLock();  // 非公平锁public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(new Task(), "线程-" + i).start();}}static class Task implements Runnable {@Overridepublic void run() {lock.lock();try {System.out.println(Thread.currentThread().getName() + " 获取了锁");Thread.sleep(1000);  // 模拟业务操作} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
}

运行结果(不同线程获取锁的顺序可能不按提交顺序进行):

线程-0 获取了锁
线程-1 获取了锁
线程-3 获取了锁
线程-2 获取了锁
线程-4 获取了锁

实现细节

  1. 线程直接尝试获取锁,如果锁是可用的,立即获取,不需要查看等待队列中的其他线程。
  2. 由于线程不按顺序排队,某些线程可能抢占其他线程的锁,从而提升了性能,但可能导致某些线程长期等待。
4.4 公平锁与非公平锁的性能比较

公平锁和非公平锁的核心区别在于锁的分配顺序上。公平锁保证了锁的顺序性,而非公平锁则允许锁的抢占。因此,它们在不同的应用场景下表现不同:

  • 公平锁:适合对顺序性要求高的场景,但由于频繁的上下文切换,性能可能较低。
  • 非公平锁:适合高并发、低延迟的场景,能够提升系统的吞吐量,但可能导致某些线程长期处于饥饿状态。

第五部分:ReentrantLock 内部如何实现公平锁与非公平锁

ReentrantLock 的内部是通过 AbstractQueuedSynchronizer(AQS)来管理锁的状态和线程队列。AQS 提供了 FIFO 队列来管理线程的等待,公平锁与非公平锁的实现主要区别在于 tryAcquire() 方法。

5.1 AQS 中的公平锁实现

对于公平锁,ReentrantLock 会首先检查等待队列中是否有其他线程,如果有,当前线程会被加入队列,等待前面的线程获取和释放锁。

代码示例:AQS 中的公平锁实现

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;}}return false;
}
5.2 AQS 中的非公平锁实现

对于非公平锁,线程在获取锁时,不会检查等待队列中的其他线程,而是直接尝试获取锁。

代码示例:AQS 中的非公平锁实现

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 非公平锁直接尝试获取锁if (c == 0 && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}return false;
}
5.3 公平锁与非公平锁的选择

ReentrantLock 提供了两种构造方法,开发者可以选择使用公平锁或非公平锁:

// 使用公平锁
ReentrantLock fairLock = new ReentrantLock(true);// 使用非公平锁
ReentrantLock unfairLock = new ReentrantLock(false);

第六部分:公平锁与非公平锁的应用场景

6.1 公平锁的应用场景

公平锁适用于对锁的顺序性要求较高的场景,典型应用包括:

  • 银行排队系统:保证客户按顺序办理业务。
  • 任务调度系统:确保任务按照提交的顺序执行,避免任务的无序执行导致结果错误。
6.2 非公平锁的应用场景

非公平锁适用于高并发、对性能要求较高的场景,典型应用包括:

  • Web服务器:在处理高并发请求时,非公平锁能够减少锁竞争带来的性能损耗。
  • 数据库连接池:非公平锁可以提高数据库连接的利用率,减少线程切换带来的开销。

第七部分:公平锁与非公平锁的性能测试

通过实际的性能测试,可以更直观地了解公平锁与非公平锁在不同并发场景下的表现。以下是一个性能测试的示例代码,用于对比公平锁与非公平锁在高并发场景下的表现。

代码示例:公平锁与非公平锁的性能测试

import java.util.concurrent.locks.ReentrantLock;public class LockPerformanceTest {private static final int THREAD_COUNT = 100;private static final ReentrantLock fairLock = new ReentrantLock(true);  // 公平锁private static final ReentrantLock unfairLock = new ReentrantLock(false);  // 非公平锁public static void main(String[] args) throws InterruptedException {testLockPerformance(fairLock, "公平锁");testLockPerformance(unfairLock, "非公平锁");}private static void testLockPerformance(ReentrantLock lock, String lockType) throws InterruptedException {long startTime = System.currentTimeMillis();Thread[] threads = new Thread[THREAD_COUNT];for (int i = 0; i < THREAD_COUNT; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 100000; j++) {lock.lock();try {// 模拟业务操作} finally {lock.unlock();}}});}// 启动所有线程for (Thread thread : threads) {thread.start();}// 等待所有线程完成for (Thread thread : threads) {thread.join();}long endTime = System.currentTimeMillis();System.out.println(lockType + " 耗时: " + (endTime - startTime) + " 毫秒");}
}

运行结果

公平锁 耗时: 3500 毫秒
非公平锁 耗时: 2900 毫秒

分析

  • 在高并发场景下,非公平锁由于减少了线程的上下文切换,性能更高。
  • 公平锁由于需要维护线程的顺序性,性能相对较低,但保证了线程的公平性。

第八部分:总结

公平锁与非公平锁是并发编程中两种常见的锁机制,它们各自具有不同的优缺点,适用于不同的应用场景。在高并发的环境下,非公平锁能够带来更高的性能,但可能会导致某些线程饥饿。而公平锁则通过维护一个FIFO队列,保证线程能够按照请求顺序获取锁,防止线程饥饿,但会带来一定的性能损耗。

ReentrantLock 提供了对公平锁与非公平锁的支持,开发者可以根据实际需求选择合适的锁类型。在需要保证任务顺序性的场景下,公平锁是较好的选择;而在对性能要求较高的场景中,非公平锁更具优势。

通过对这两种锁的实现原理、性能对比以及应用场景的分析,开发者可以更好地理解并选择适合自己的锁机制,从而提高系统的并发性能和稳定性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1549251.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

最详细!适合AI大模型零基础入门的学习路线+学习方法+学习资料,全篇干货,建议收藏!

前言 随着ChatGPT的横空出世&#xff0c;大模型时代正式来临。千亿甚至万亿参数的大模型陆续出现&#xff0c;各大企业、高校纷纷推出自己的大模型&#xff0c;这标志着通用智能时代的到来。对于零基础的初学者来说&#xff0c;如何快速入门AI大模型&#xff0c;抓住这个时代的…

别再使用[]来获取字典的值了,来尝试一下这些方法

字典 在Python中&#xff0c;字典&#xff08;Dictionary&#xff09;是一种非常灵活的数据结构&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。每个键都是唯一的&#xff0c;并且与某个值相关联。字典是Python中处理映射关系&#xff08;即一个键对应一…

数据结构与算法——Java实现 22.有效的括号

目录 22. 有效的括号 思路 接口 数组实现类 有效的括号 力扣 直到有一天&#xff0c;我不会再问离开的人为什么 —— 24.9.28 22. 有效的括号 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。…

【网站推荐】favicon图标生成

在制作网站的过程中&#xff0c;图标和 Favicon 是不可或缺的一部分。Favicon 是浏览器标签、书签和其他地方显示的小图标&#xff0c;它不仅可以增强网站的品牌识别度&#xff0c;还能提升用户体验。一个精美的 Favicon 可以让您的网站在众多标签中脱颖而出&#xff0c;吸引用…

信息学奥赛复赛复习06-CSP-J2020-02直播获奖-向上取整、向下取整、整数除法、最大值、最小值、计数排序

PDF文档回复:20240928 1 2020 CSP-J 题目1 优秀的拆分 [题目描述] NOI2130 即将举行。为了增加观赏性&#xff0c;CCF 决定逐一评出每个选手的成绩&#xff0c;并直播即时的获奖分数线。本次竞赛的获奖率为 w%&#xff0c;即当前排名前 w% 的选手的最低成绩就是即时的分数线 …

集合框架 - Map双列集合

01 概述 02 常用方法 03 遍历方式 【快捷键】&#xff1a;ctrlaltv 【说明】&#xff1a;Map.Entry<xx,xx>中&#xff0c;Entry是Map集合中的一个接口&#xff0c;但接口是不能创建对象的&#xff0c;它底层是通过使用Entry的实现类对象来封装键值对数据的。 【说明】&a…

LLM大模型学习:致AI新手掌握这些经验,助你少走三年弯路!

这篇文章&#xff0c;我将结合自己在大模型领域的经验&#xff0c;给大家详细聊聊新人应该如何转行大模型赛道&#xff1f; 比如大模型都有哪些方向&#xff1f;各方向的能力要求和岗位匹配&#xff1f;新手转行大模型常踩的坑和常见的误区&#xff1f;以及入行大模型最顺滑的…

代码为笔,合作作墨,共绘共赢画卷———未来之窗行业应用跨平台架构

合作共赢&#xff0c;代码同创&#xff0c;成就非凡 一、资源整合方面 1.1. 技术资源共享 - 不同的合作伙伴可能在技术领域各有所长。例如&#xff0c;一方可能擅长前端用户界面设计&#xff0c;具有丰富的交互设计经验&#xff0c;能够打造出美观、易用的预订界面&#xff…

Hadoop三大组件之MapReduce(一)

Hadoop之MapReduce 1. MapReduce是什么 MapReduce是一个分布式运算程序的编程框架&#xff0c;旨在帮助用户开发基于Hadoop的数据分析应用。它的核心功能是将用户编写的业务逻辑代码与自带的默认组件整合&#xff0c;形成一个完整的分布式运算程序&#xff0c;并并发运行在一…

Codeforces Round 975 (Div. 2)

传送门&#xff1a;https://codeforces.com/contest/2019 B. All Pairs Segments 题意&#xff1a; 首先样例解释一下&#xff1a; 一共有&#xff1a;[1,2],[1,3],[1,5],[1,6],[1,7],[2,3],[2,5],[2,6],[2,7],[3,5],[3,6],[3,7],[5,6],[5,7],[6,7] 点 1&#xff0c;7 在5个…

使用MessagePipe实现进程间通信

1、MessagePipe介绍 可以用于.NET和Unity上面的高性能的内存/分布式消息传递管道。适用于发布/订阅模式、CQRS的中介模式、Prism中的EventAggregator、IPC&#xff08;进程间通信&#xff09;-RPC等。 支持&#xff1a; 依赖注入过滤器管道更好的事件同步/异步带键值的/无键…

c++11~c++20 内联命名空间

在工作&#xff0c;我们经常会引入第三方库&#xff0c;偶尔会碰到同名的函数和类型&#xff0c;造成编译冲突的问题。一般我们可以使用命名空间&#xff0c;例如 #include <iostream> #include <iostream> using namespace std;namespace S1 {void foo(){cout &l…

基于python数据采集的可视化数据大屏,数据驱动的界面。

众所周知&#xff0c;可视化大屏离不开数据的采集&#xff0c;正式有了各种格式化的数据供给&#xff0c;可视化大屏才千姿百态&#xff0c;在数据采集方面&#xff0c;python优势什么明显&#xff0c;为大家分享一下。 一、python是什么 Python是一种高级、通用、解释型编程…

服装品牌小程序展示承载服务

服装大小品牌众多&#xff0c;还包括多区域的门店商家合作批发、咨询等&#xff0c;品牌或经销商想要获得更多生意&#xff0c;线上渠道往往是必备的&#xff0c;品牌宣传、获客转化及持续的信息干货输出等。 线上渠道多样化&#xff0c;尤其是微信、百度、抖音、快手等平台聚…

具身智能综述:鹏城实验室中大调研近400篇文献,深度解析具身智能

具身智能是实现通用人工智能的必经之路&#xff0c;其核心是通过智能体与数字空间和物理世界的交互来完成复杂任务。近年来&#xff0c;多模态大模型和机器人技术得到了长足发展&#xff0c;具身智能成为全球科技和产业竞争的新焦点。然而&#xff0c;目前缺少一篇能够全面解析…

Linux进程切换以及调度算法

目录 Linux进程切换以及调度算法 Linux进程切换介绍 前置知识 进程切换过程分析 调度算法 补充知识 Linux进程切换以及调度算法 Linux进程切换介绍 前面提到&#xff0c;分时操作系统下&#xff0c;CPU会在多个进程中做高速切换以实现多个进程看似同时执行的&#xff0…

防伪溯源查询系统V1.0.5

多平台&#xff08;微信小程序、H5网页&#xff09;二维码扫码输码防伪溯源查询系统&#xff0c;拥有强大的防伪码生成功能&#xff08;内置多种生成规则&#xff09;、批量导出防伪码数据、支持代理商管理端&#xff08;可批量对自己防伪码进行操作处理&#xff09;、文章资讯…

【深度学习】(10)--ResNet残差网络

文章目录 ResNet残差网络1. 传统卷积神经网络的问题1.1 梯度消失和梯度爆炸1.2 退化问题 2. 解决问题2.1 梯度消失与爆炸2.2 退化问题 3. 残差结构结构归纳 4. BN&#xff08;Batch Normalization&#xff09; 总结 ResNet残差网络 ResNet 网络是在 2015年 由微软实验室中的何…

ComfyUI | 好用的人体 衣服分割工具-③-Layer Style | 超多实用功能 | 强烈推荐

这里为大家分享检测人体的脸部、五官、头发、手臂、腿、脚&#xff0c;上衣、裤子、背景的插件&#xff0c;能够生成出对应的蒙版mask&#xff0c;接入到ComfyUI中&#xff0c;用于后续处理&#xff0c;如局部重绘&#xff0c;换背景等。 &#xff08;需要相关插件的同学可自…

华为LTC流程架构分享

文末附LTC流程管理PPT下载链接~ 前面笔者分享了华为LTC流程相关PPT&#xff0c;应读者需求&#xff0c;今天从架构角度进行再次与读者共同学习下LTC流程架构。 华为LTC流程架构是一个全面且集成的业务流程体系&#xff0c;从线索发现开始&#xff0c;直至收回现金&#xff0c…