目录1、锁1.1、作用1.2、维度1.3、目的2、常用锁2.1、synchronized2.2、ReentrantLock2.3、ReadWriteLock3、偏向锁和轻量级锁3.1、Biased Locking3.2、Lightweight Locking4、悲观锁和乐观锁4.1、悲观锁Pessimistic Lock4.2、乐观锁Optimistic Lock5、公平锁和非公平锁5.1、非公平锁5.2、公平锁与非公平锁比较6、多线程并发工具前言关于本章节介绍的锁的升级和分类线程并发工具、cas等详细知识点可参考本人其他章节1、CAS机制原理-CSDN博客2、java死锁和活锁-CSDN博客3、synchronized和volatile的介绍-CSDN博客4、多线程并发控制工具Semaphore的学习-CSDN博客5、关于ReadWriteLock读写锁的介绍-CSDN博客概述关于锁的状态变化如下图所示1、锁1.1、作用在 Java 中锁是一种同步机制。用于防止多个线程同时访问同一资源。通过为临界区代码块共享资源的访问代码加锁只有获得锁的线程才能执行这些代码而其他线程需要等待直到锁被释放。使用锁能够帮助我们解决多个线程同时访问共享资源时可能出现的各种问题确保线程安全。如下图所示1.2、维度可以从设计态度内部实现等级、调度策略、能力特征来分析同一把锁的四维坐标示例synchronized:悲观独占可重入非公平内部等级自动演化new ReentrantLock(true):悲观独占可重入公平 AQS队列管理AtomicInteger:乐观无锁(基于 CAS 循环不存在传统意义上的锁实体)等待策略:获取锁失败时线程是自旋(Spin, 占用 CPU 空转) 还是阻塞挂起(Block, 承担上下文切换成本)“自旋锁”、“适应性自旋”属于这一维度。synchronized 在轻量级阶段会尝试自旋在重量级阶段则阻塞。1.3、目的为什么要使用锁在多线程编程中使用锁的主要理由包括1 、确保数据一致性当多个线程同时访问和修改共享数据时可能会导致数据不一致。使用锁可以确保在某个时刻只有一个线程对数据进行修改避免数据被多个线程同时修改而导致的不一致。2、防止竞争条件指两个或多个线程同时尝试访问并修改共享资源可能导致不可预知的结果。锁可以确保同一时间只有一个线程能够访问资源。3、避免死锁虽然锁本身可能会导致死锁但在设计时采用合适的锁机制和策略能够减少或避免死锁的发生。控制锁的使用和顺序可以使代码更安全。4、维护线程安全使用锁是确保线程安全的一种方式。只有获得锁的线程才能继续执行代码这样可以确保代码的意图和状态不会被意外改变。小结使用锁在多线程编程中是必不可少的能够有效防止数据不一致、竞争条件和保证线程安全。Java 提供了多种锁机制包括内置锁、显式锁和读写锁等可以根据具体的应用场景灵活选择合适的锁来提高程序的安全性和性能。那么基于以上的理解可选择合适的锁2、常用锁2.1、synchronizedsynchronized 并非一种静态的锁类型而是 JVM 提供的同步入口。根据竞争激烈程度JVM 会在无锁 → 偏向 → 轻量级 → 重量级之间动态演化。JDK 15 起偏向锁被官方废弃默认关闭后续版本移除。原因撤销偏向需要在 Safepoint 停止所有线程STW代价高昂且现代应用多线程已是常态单线程访问的场景大幅减少偏向锁收益不再显著。1.Mark Word锁状态的载体对象头中的Mark Word是锁状态的存储位置。它就像锁的“户口本”在不同状态下存储不同内容2.升级路径与不可逆性经典的 HotSpot 模型下锁升级过程是只升不降的[初始] [冲突发生] [竞争加剧] [严重竞争]无锁 ──────▶ 偏向锁(已废弃/JDK15) ───▶ 轻量级锁 ──────────▶ 重量级锁│ │ │▼ ▼ ▼撤销偏向 CAS 抢占失败 Monitor 等待队列(进入自旋) (线程 Park 挂起)每一级升级都是因为前一级的成本模型不再划算无锁 → 偏向检测到线程复用仅 JDK 15 有效。偏向 → 轻量出现第二个线程竞争。轻量 → 重量自旋超过阈值或频繁 CAS 失败上下文切换成本低于自旋成本。3.自旋 vs 阻塞理解锁升级的关键在于权衡两种等待方式的开销操作成本量级说明线程挂起/唤醒微秒级 (µs)涉及用户态到内核态的切换OS 调度开销大。自旋空转一轮纳秒级 (ns)仅消耗 CPU 周期无 OS 干预。结论当临界区执行时间 两次上下文切换总耗时 时忙等待自旋优于阻塞。这正是轻量级锁存在的理论基础。适应性自旋 (Adaptive Spinning)JVM 会记录同一把锁上历史自旋的成功率。此前很快成功 → 适当延长本次自旋次数。此前多次失败 → 尽早放弃自旋直接升级为重量级锁。2.2、ReentrantLock1、简介是 Java 中的一个可重入锁比内置的 synchronized 拥有更灵活的控制机制属于 java.util.concurrent.locks 包。可以锁住多个线程并且同一个线程可以多次获取同一把锁即重入。线程在第一次获取锁后下一次再次请求此锁时可以成功而不会导致死锁。在使用可重入锁时线程需要在释放锁的次数等于获得锁的次数时才能真正释放锁。2、底层原理实现依赖于 AQSAbstractQueuedSynchronizer它使用一个二元信号量state和一个 FIFO 队列来管理线程的互斥访问。每当一个线程请求锁时如果锁被其他线程占用AQS 会将此线程加入等待队列。通常会使用在队列里面可参考文章深入了解阻塞队列-CSDN博客1.状态统计:内部维护一个整数标志 state表示当前锁的状态数值表示当前获得此锁的线程的重入次数。0 表示锁未被占用正数表示锁被占用2.线程队列:当线程请求锁时如果锁已被其他线程占用线程会被放入一个等待队列这是通过 AQS 的队列实现的。3.公平性与非公平性:它的公平性和非公平性取决于构造函数的参数。默认情况下构造一个ReentrantLock对象时它使用的是非公平策略这意味着它在获取锁时不确保线程的顺序。公平锁: 按照请求锁的顺序来获取锁。即使请求锁的线程非常少也会从头开始请求。非公平锁: 线程不按请求顺序获取锁因此新请求的线程可以抢占锁。默认情况下ReentrantLock 是非公平的。代码如下所示3、特点可重入性:同一个线程可以多次获得同一个锁而不会造成死锁。公平性选择:在创建 ReentrantLock 时可以选择使用公平策略即按照 FIFO 顺序获取锁避免大线程一直占用锁而导致小线程被饿死。可中断:线程在获取锁时可以响应互中断。锁定超时:可以尝试以指定时间获取锁防止无尽的等待。条件变量:可以使用 Condition 对象来实现更高效的线程间协调。4、常用方法lock():获取锁。unlock():释放锁必须确保在 finally 块中调用以防止意外死锁。tryLock():尝试获取锁如果成功则返回 true如果未获得则返回 false而不阻塞。tryLock(long timeout,TimeUnit unit):尝试在指定时间内获取锁。newCondition():创建与此锁绑定的条件变量。示例import java.util.concurrent.locks.ReentrantLock; class ReentrantLockExample { private final ReentrantLock lock new ReentrantLock(); private int count 0; public void increment() { lock.lock(); // 获取锁 try { count; } finally { lock.unlock(); // 一定要释放锁防止死锁 } } public int getCount() { return count; } } public class Main { public static void main(String[] args) throws InterruptedException { ReentrantLockExample example new ReentrantLockExample(); Thread thread1 new Thread(() - { for (int i 0; i 1000; i) example.increment(); }); Thread thread2 new Thread(() - { for (int i 0; i 1000; i) example.increment(); }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Final count: example.getCount()); } }5、使用场景粒度控制:当需要在多线程环境中显式控制共享资源的访问时ReentrantLock 是一个不错的选择。需要条件变量:当需要复杂的线程协调和条件监测时可以利用 ReentrantLock 的 Condition。2.3、ReadWriteLock关于更多介绍可参考关于ReadWriteLock读写锁的介绍-CSDN博客1、简介是一种分离读和写操作的锁允许多个线程同时读取资源但在写入时保证排他性提高了在读多写少的场景中的性能。ReentranReadWriteLock 类是 ReadWriteLock 接口的一个具体实现。2、底层原理代码如下所示主要的实现机制读锁和写锁的计数:读锁: 当第一个读线程获取读锁时计数器加一。每当另一个读线程请求读锁时如果条件允许则允许它获取读锁计数器加一。写锁: 当线程获取写锁时AQS 状态会变为表示写锁持有者的值。此时写线程的持有者是唯一的其他所有请求读锁和写锁的线程都将被阻塞。条件与状态的管理:线程在请求写锁时会检查当前是否有线程持有读锁或写锁。如果有写操作将被阻塞。当释放锁时适当地减少计数或改变状态以允许其他线程获取锁。3、实现细节内部有两个简单的锁示例ReadLock 和 WriteLock分别用于管理读和写操作。4、主要的方法获取读锁 (lock()):在获取读锁时线程数字增加计数器如果当前没有线程持有写锁则允许获取读锁。释放读锁 (unlock()):减少读锁计数器根据计数器的值判断是否需要释放对写锁的阻塞。获取写锁 (lock()):当线程尝试获取写锁时它将检查当前读锁的计数器是否为零如果为零则可以获取锁否则将此线程置于等待状态。释放写锁 (unlock()):将锁的状态重置为未持有并唤醒所有等待线程。以下是简化版的伪代码展示 ReentranReadWriteLock 的基本逻辑class ReentrantReadWriteLock { private final ReadLock readLock new ReadLock(this); private final WriteLock writeLock new WriteLock(this); private int readerCount 0; // 当前读者数量 private Thread writerThread null; // 当前写者 private int writeAccessCount 0; // 当前写操作的计数 public void lockRead() { synchronized (this) { while (writeAccessCount 0) { wait(); // 等待 } readerCount; // 增加读者数量 } } public void unlockRead() { synchronized (this) { readerCount--; // 减少读者数量 if (readerCount 0) { notifyAll(); // 通知已等待的写者 } } } public void lockWrite() { synchronized (this) { while (writerThread ! null || readerCount 0) { wait(); // 等待 } writerThread Thread.currentThread(); // 获取写锁 writeAccessCount; } } public void unlockWrite() { synchronized (this) { writerThread null; // 清空当前写者 writeAccessCount--; // 写操作计数减一 notifyAll(); // 通知所有其他线程 } } }5、特点读锁:可以被多个线程同时获取当没有写线程持有写锁时读锁可以自由获取。写锁:是独占的只有持有写锁的线程可以对数据进行写操作。如果已有人持有写锁其他任何线程不论是读线程还是写线程都不能获取锁。公平性锁取决于创建时候传入的参数为ture或者false默认是不公平锁可以通过构造 ReentranReadWriteLock 时传入 true 来创建公平锁ReentrantReadWriteLock lock new ReentrantReadWriteLock(true);6、使用场景在读操作远远多于写操作的场景中如缓存机制、共享数据结构等代码如下所示import java.util.concurrent.locks.ReentrantReadWriteLock; class SharedResource { private final ReentrantReadWriteLock lock new ReentrantReadWriteLock(); private int data 0; public void write(int value) { lock.writeLock().lock(); try { data value; System.out.println(Written: value); } finally { lock.writeLock().unlock(); } } public int read() { lock.readLock().lock(); try { System.out.println(Read: data); return data; } finally { lock.readLock().unlock(); } } } public class Main { public static void main(String[] args) throws InterruptedException { SharedResource sharedResource new SharedResource(); // Create writer thread Thread writer new Thread(() - { for (int i 0; i 5; i) { sharedResource.write(i); } }); // Create reader thread Thread reader new Thread(() - { for (int i 0; i 5; i) { sharedResource.read(); } }); writer.start(); reader.start(); writer.join(); reader.join(); } }总结1.高并发场景:在读多写少的场景如缓存、配置管理等中ReentranReadWriteLock 显著提高了性能因为多个线程可以同时读取数据而在写操作时则保证安全。2.数据共享:适用于多个线程对共享数据资源的安全读写操作。公平锁适合需要更严格线程调度的场景而非公平锁适合性能优先、可容忍一定不公平的场合。3、偏向锁和轻量级锁3.1、Biased Locking1、简介是 Java 6 中引入的一种优化机制通过标记偏向某个线程而不需要执行加锁和解锁的操作第一次获取锁时线程会被“偏向”到该锁上。目的为了减少无竞争状态下的锁获取和释放的开销允许一个线程在没有竞争的在高并发环境中频繁的获取和释放锁会导致性能下降。2、底层原理可偏向锁的原理是基于线程ID的。下面是其具体工作流程1.锁的偏向:当一个线程第一次获取锁时JVM 将该线程的 ID 记录在锁的对象头中并且这个对象的状态将被设置为偏向模式。后续同一线程的所有锁定操作都将直接获取锁通过检查对象头中的线程ID省掉了传统的加锁流程。2.锁的撤销:如果有其他线程尝试获取这个偏向锁则锁的状态会被撤销转为重量级锁如自旋锁或不可重入锁。偏向锁会被撤销并转换为轻量级锁或重量级锁允许其他线程进行获取。3.锁的回退:JVM 可能会在多线程竞争激烈时撤销偏向锁并转换为轻量级锁适应当前的竞争需求。3、特性适用于无竞争状态:可偏向锁的优化在于当只有一个线程访问资源时能显著减少锁的开销。锁膨胀:当存在线程竞争时偏向锁会升级为轻量级锁或重量级锁因此会存在锁的膨胀过程。不公平性:如果一个线程持有偏向锁另一个线程可以直接竞争并强制获取锁。即即使后到的线程会打断在队列中已经存在的线程使得偏向锁并不遵循公平原则。4、示例可偏向锁在开发中是自动管理的开发者不能直接控制偏向锁的使能或禁用。具体的使用示例通常是关注在如何使用 synchronized 关键字或者使用显示锁而不需要特别配置偏向锁。以下是一个简单的使用 synchronized 的示例但不直接能展示偏向锁的内部运作可以使用 JVM 参数来检查偏向锁的状态。代码如下所示public class BiasedLockingExample { private static int counter 0; public synchronized void increment() { counter; } public static void main(String[] args) throws InterruptedException { BiasedLockingExample example new BiasedLockingExample(); Runnable task () - { for (int i 0; i 1000; i) { example.increment(); } }; Thread thread1 new Thread(task); Thread thread2 new Thread(task); // 启动线程 thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Final counter value: counter); } }5、使用场景适用于线程竞争不激烈的场景如短时间内同一线程多次访问共享资源。3.2、Lightweight Locking1、简介是一种在无竞争的情况下可以有效提升性能的锁。当对象的锁被一个线程持有时其它线程尝试获取锁时会使用 CASCompare And Swap算法进行尝试。轻量级锁是相对于重量级锁如 synchronized 锁而言的。轻量级锁的设计使得在没有锁竞争时能够快速地获取锁而在锁竞争发生时则提升至更为复杂的锁策略比如自旋锁或偏向锁。2、底层原理当一个线程调用同步块时JVM 将检查这个对象的锁状态。如果没有其他线程持有锁线程获取锁并将对象的锁标志位设置为持有线程的 ID。如果有其他线程也试图获取锁重定向到传统的互斥锁。下面是轻量级锁的基本工作流程锁的状态管理:在对象头中有一个指示锁状态的字段通常称为 Mark Word。这个字段用于存储锁的状态信息包括锁的持有者和锁的状态。当一个线程尝试获取轻量级锁时首先会查看锁的状态。如果锁未被持有即状态可用该线程将会尝试通过 CAS 操作将该锁的状态从无锁状态0修改为持有锁状态标记为当前线程ID并成功获取锁。自旋获取锁:如果另一个线程已经持有锁即竞争发生待获取锁的线程将会被放入线程栈中并消耗一定的 CPU 时间即自旋尝试通过不断重复的 CAS 操作来获取锁。自旋的次数通常是有限的这样可以控制 CPU 的使用。重量级锁转化:如果 CAS 操作失败即存在其他线程持有锁且重入次数增加轻量级锁将转化为重量级锁这时候将进入阻塞状态。此时系统会使用更重的锁机制如传统的自旋锁和挂起锁来管理线程的竞争。3、特点快速:当只有一个线程在使用锁的时候它的开销非常小不需要太多的“办理手续”。竞争的时候升级:如果有多个线程同时尝试获取这个锁它们就会进入一个等待状态就像朋友们需要排队借书一样。自动升级:当竞争变得激烈时轻量级锁会“升级”成更复杂的锁就像图书馆可能会要求更多的手续来管理借书这样可以更好地处理多个线程的请求。4、比喻说明想象一个共享图书馆假设你和几个朋友想要看同一本书。普通锁就像图书馆的一本普通书。每个人都需要在把书借出去的时候填写借书单这很耗时。但是只有一个人能借到书其他人必须等待。轻量级锁的工作方式如果只有一个人看这本书那么来借书的朋友只需要快速在书上写下他们的名字这个过程非常快。只有当有多个朋友同时想借书时图书馆才会要求大家排队填写借书单。如果队伍太长图书馆可能会想办法快速处理比如说“排队太长了我就关掉填写表格并直接让大家进来变成普通借书”。5、 实现方式轻量级锁的实现主要通过以下几个步骤竞争转化:通过线程在尝试获取锁时反复使用 CAS 操作如果获得成功则进入临界区如果失败则将锁膨胀为重量级锁。对象头结构:使用对象头中的Mark Word来存储锁的状态。Mark Word 结构中包括一些状态位用于指示锁是无锁、轻量级锁、偏向锁或重量级锁。自旋次数控制:为了避免长时间自旋带来的 CPU 浪费轻量级锁在实现中通常会限制自旋的次数。6、代码示例通过synchronized和lock 关键字来隐式实现的。Java 的 JVM 会根据状态自适应锁的类型偏向锁、轻量级锁、重量级锁等管理。代码如下public class LightweightLockExample { private int count 0; public void increment() { synchronized (this) { // 使用synchronized可能会使用轻量级锁 count; } } public int getCount() { return count; } public static void main(String[] args) throws InterruptedException { LightweightLockExample example new LightweightLockExample(); Runnable task () - { for (int i 0; i 1000; i) { example.increment(); } }; Thread thread1 new Thread(task); Thread thread2 new Thread(task); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Final count: example.getCount()); } }7、使用场景当多个线程同时尝试访问某个资源时如何在保证安全不出错的同时又尽可能快地处理这些请求。4、悲观锁和乐观锁4.1、悲观锁Pessimistic Lock假设每次操作都会发生冲突因此总是使用锁来保护数据。例如synchronized 和 ReentrantLock。1、定义:是一种在获取锁时假设会发生数据竞争的锁策略。也就是说当一个线程要访问共享资源时它总是认为会有其他线程同时竞争这个资源因此在访问前会先加锁以确保资源的独占访问。2、原理:当线程请求共享资源时如果这个资源被其他线程占用当前线程将被阻塞直到资源被释放。一旦获得锁当前线程可以自由访问资源直到它显式释放锁或线程结束。悲观锁的典型实现方式是使用 synchronized 或 ReentrantLock。3、优点:确保数据的一致性和安全性。简单易懂容易实现。4、缺点:在高竞争环境下可能会导致性能降低因为线程经常会被阻塞造成上下文切换的开销。可能导致资源的浪费特别是长时间等待的线程。使用 synchronized 来实现悲观锁public class PessimisticLockExample { private int count 0; public synchronized void increment() { count; } public int getCount() { return count; } public static void main(String[] args) throws InterruptedException { PessimisticLockExample example new PessimisticLockExample(); Thread thread1 new Thread(() - { for (int i 0; i 1000; i) { example.increment(); } }); Thread thread2 new Thread(() - { for (int i 0; i 1000; i) { example.increment(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Final count: example.getCount()); } }4.2、乐观锁Optimistic Lock假设不会发生冲突每次操作都直接执行在做提交时检查是否有数据冲突。通常使用版本号如 CAS 操作实现。1、定义:是一种在获取锁时假设不会发生数据竞争的锁策略。也就是说在访问共享资源时线程不会立即加锁而是先执行操作然后在提交时检查是否有竞争发生。2、原理:标记版本:乐观锁通常使用版本号或时间戳等方式来标识资源的状态。在执行操作时线程会先读取数据并进行操作当线程准备提交更改时它会检测在此过程中是否有其他线程修改了数据例如通过检查版本号。如果检测到版本号变化说明有其他线程修改了数据当前线程将会重试操作如果没有变化则可以安全地提交更改。3、实现方式:乐观锁的常见实现方式是使用 CASCompare and Swap算法。例如java.util.concurrent.atomic 包中的类如 AtomicInteger就使用乐观锁的原理。4、优点:在绝大多数情况下无需阻塞性能较高。避免了死锁的可能性。5、缺点:在高并发场景下冲突可能比较频繁如果频繁失败可能反而造成性能损耗。需要处理重试逻辑非常依赖于数据的版本控制。实现示例乐观锁下面是使用 AtomicInteger 实现乐观锁的代码示例import java.util.concurrent.atomic.AtomicInteger; class OptimisticLockExample { private final AtomicInteger count new AtomicInteger(0); public void increment() { count.incrementAndGet(); // CAS 操作 } public int getCount() { return count.get(); } } public class Main { public static void main(String[] args) throws InterruptedException { OptimisticLockExample example new OptimisticLockExample(); Thread thread1 new Thread(() - { for (int i 0; i 1000; i) example.increment(); }); Thread thread2 new Thread(() - { for (int i 0; i 1000; i) example.increment(); }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Final count: example.getCount()); } }2.5.3、联系及区别4、总结ReentrantLock: 适合需要显式锁控制和更复杂的锁管理机制的场景。ReadWriteLock: 在读操作多于写操作时使用能优化性能。偏向锁和轻量级锁: 适用于低竞争的场景在大多数情况下能有效减少锁的开销。悲观锁和乐观锁: 根据预期的竞争情况选择锁的类型乐观锁在竞争较小的情况下更高效。5、公平锁和非公平锁5.1、非公平锁1、定义非公平锁意味着在获取锁的请求上不会遵循严格的 FIFO先进先出顺序。新请求的锁可以jump到队列前面获取锁而不必等到已有线程释放锁。原因为什么 JDK 默认非公平公平锁成本唤醒队列中沉睡的线程需要支付一次上下文切换成本微秒级。非公平优势刚到达的线程通常是“热”的正在 CPU 上运行让它直接抢占锁可以省去唤醒旧线程的成本。结果虽然可能导致个别线程饥饿但整体系统的吞吐量更高。⚠️注意即使是公平锁也不具备“绝对公平”。持有锁的线程重入时无需排队享有最高优先级。公平性仅体现在竞争失败后的唤醒顺序上。2、特征竞争优先: 最新请求锁的线程可能获得锁即使有其他线程在等待。可能会导致线程饥饿: 如果一个线程总是比等待队列中的线程更早到达获取锁的机会它可能会一直抢占锁这可能导致某些线程长时间无法获取锁。3、实现实现依赖于内部的锁状态管理。当一个线程请求锁并发现锁已经被占用时它会将自己添加到等待队列中。但在下次竞争该锁时线程会尝试以“直接竞争”的方式去获取锁不管其在队列中的位置。// NonfairSync.lock() - 非公平逻辑 final void lock() { // ① 第一步动作直接尝试 CAS 抢占不管队列里有没有人在等 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // ② 抢占失败才进入排队流程 } // FairSync.lock() - 公平逻辑 final void lock() { acquire(1); // 直接进入排队CAS 前需检查 hasQueuedPredecessors() }换句话说它尝试在某些情况下直接获取锁而不管后续的线程是否已经在队列中等待。5.2、公平锁与非公平锁比较通过构造函数参数可以决定可重入锁的公平性公平锁 (ReentrantLock(true)):称为公平锁线程按照请求锁的顺序来获得锁只有当前没有其他线程持有锁的情况下排队中的第一个线程才能获取锁。非公平锁 (默认的 ReentrantLock()):新请求的线程可以“抢占”锁。代码示例import java.util.concurrent.locks.ReentrantLock; public class FairLockExample { private static final ReentrantLock fairLock new ReentrantLock(true); // 公平锁 private static final ReentrantLock unfairLock new ReentrantLock(); // 非公平锁 public static void main(String[] args) throws InterruptedException { Runnable fairRunnable () - { for (int i 0; i 5; i) { fairLock.lock(); try { System.out.println(Thread.currentThread().getName() acquired fair lock.); } finally { fairLock.unlock(); } } }; Runnable unfairRunnable () - { for (int i 0; i 5; i) { unfairLock.lock(); try { System.out.println(Thread.currentThread().getName() acquired unfair lock.); } finally { unfairLock.unlock(); } } }; Thread threadA1 new Thread(fairRunnable, Thread A1); Thread threadA2 new Thread(fairRunnable, Thread A2); Thread threadB1 new Thread(unfairRunnable, Thread B1); Thread threadB2 new Thread(unfairRunnable, Thread B2); threadA1.start(); threadA2.start(); threadB1.start(); threadB2.start(); threadA1.join(); threadA2.join(); threadB1.join(); threadB2.join(); } }总结ReentrantLock默认采用非公平策略这使得线程在请求锁时不会严格遵循请求的顺序因此能够在高竞争的情况下容易获取锁。公平模式可以避免线程饥饿但可能带来额外的性能开销使用场景应考虑具体需求和性能影响。6、多线程并发工具Semaphore类是Java中用于控制并发访问共享资源的工具通过维护一个计数器来限制同时访问某个资源的线程数量。信号量的核心概念包括许可数量、计数器和公平性。线程通过acquire()方法请求许可计数器减一通过release()方法释放许可计数器加一。Semaphore可以配置为公平或非公平公平模式遵循FIFO原则。其典型使用场景包括数据库连接池和文件句柄管理以避免资源竞争和系统过载。尽管Semaphore可以帮助减少死锁风险但它并非直接解决死锁的工具。合理使用Semaphore可以提高并发程序的安全性和效率。更多Semaphore类详细的内容可参考多线程并发控制工具Semaphore的学习-CSDN博客面试题有队列为什么是非公平的虽然 ReentrantLock 确实维护了一个等待队列这个队列是基于 AQS 实现的但在非公平模式下当一个线程申请锁时即使它已经处于等待队列中如果在同一时刻有其他线程释放了这个锁非公平锁会优先考过滤策略允许那个请求锁的线程绕过队列中的其他线程。这种行为会导致竞争对锁的优先级:在启用了非公平策略的情况下如果有很多线程竞争锁则新到的线程可以“抢占”其他等待线程即使其他线程早已在队列中等待并优先得到锁。这是通过 CAS 操作来实现的。