当前位置: 首页 > news >正文

锁框架-面试

        Java 的并发锁机制主要分为两大体系:一是基于 synchronized 关键字的内置锁(或监视器锁),二是 java.util.concurrent.locks 包下的显式锁框架。

内置锁

定义

        内置锁的核心是:synchronized 关键字,这是 Java 最原始、最基本的锁机制,由 JVM 底层实现和管理。

用法

  • 同步代码块synchronized(object) { ... },需指定一个锁对象。

  • 同步实例方法public synchronized void method() { ... },锁对象是当前实例 this

  • 同步静态方法public static synchronized void method() { ... },锁对象是当前类的 Class 对象。

特性

  • 互斥性:同一时间只有一个线程可以进入被锁保护的代码区域。

  • 可重入性:一个线程可以多次获取自己已经持有的锁(锁计数)。

  • 非公平锁:内置锁不保证线程获取锁的顺序,默认是非公平的。

  • 不可中断:等待获取锁的线程无法被中断,会一直阻塞直到获取到锁。

  • 自动释放:当同步代码块执行完毕或发生异常时,锁会自动释放,无需手动操作。

优点

        使用简单,JVM 会自动优化(如锁升级),不易出错。

缺点

        功能相对单一,缺乏灵活性(如无法尝试非阻塞地获取锁、无法设置超时、无法实现公平锁等)。

显式锁框架

定义

        java.util.concurrent.locks包,其核心接口是 Lock

场景

锁类型核心特性与描述适用场景优点缺点与注意事项
ReentrantLock
(首选替代方案)
可重入的独占锁。提供了比 synchronized 更丰富的功能:可中断、超时获取、公平性选择。1. 需要 synchronized 所缺乏的高级功能(如可中断、超时)的场景。
2. 需要实现公平性策略的场景。
3. 锁的获取和释放不在同一个代码块(方法)中。
1. 功能强大且灵活。
2. 性能与 synchronized 相差无几(甚至在某些情况下更优)。
3. 提供了丰富的 API。
1. 必须手动释放锁(必须在 finally 块中 unlock()),否则会导致死锁。
2. 代码编写稍显复杂。
ReentrantReadWriteLock
(读写分离场景)
读写锁分离。遵循读读共享、读写互斥、写写互斥的原则。读多写少的并发场景。
例如:
- 缓存系统
- 资源注册表
- 频繁查询但很少修改的配置数据
1. 极大提升读操作的并发性能,允许多个线程同时读。
2. 保证了写操作时的独占性和数据一致性。
1. 写锁饥饿:如果读线程源源不断,写线程可能长时间等待。
2. 在读少写多或竞争激烈的场景下,性能可能不如普通独占锁(因为内部实现更复杂)。
3. 同样需手动释放锁。
StampedLock
(极致读性能优化)
提供了三种模式的锁:写锁、悲观读锁和乐观读。乐观读是一种无锁的读取,性能极高。读操作远远远远大于写操作,并且对读性能有极致要求的场景。
例如:
- 高性能的统计、监控数据访问
- 金融行情报价系统
1. 乐观读模式性能远超读写锁,因为它完全不加锁。
2. 所有方法都返回一个印戳(Stamp),用于解锁或验证。
1. API 非常复杂,容易用错。
2. 不是可重入锁,同一个线程重复获取锁会导致死锁。
3. 不支持条件变量 (Condition)。
4. 悲观读锁和写锁不区分读写线程,可能导致饥饿。

实现核心

        显示锁将其所有的同步机制(如获取锁、释放锁)都委托给了一个内部的 AQS 子类(Sync)来实现。

AQS锁框架定义

AQS(AbstractQueuedSynchronizer)是Java并发包中的一个抽象类,用于构建锁或其他同步组件的基础框架。它通过一个FIFO队列(同步队列)管理线程的阻塞与唤醒,并提供模板方法供子类实现自定义同步逻辑(如独占锁或共享锁)。

通俗理解

我们把 AQS 想象成一套标准的、自动化的医院挂号排队管理系统。这套系统的核心任务是:管理一大堆想要挂专家号(竞争共享资源)的人(线程),保证秩序不乱。


状态变量 (State) - “还剩几个号?”
  • 通俗理解:挂号大厅里有一个巨大的电子显示屏,上面显示着 当前剩余号数

    • 比如 Semaphore(信号量)允许多个线程同时访问,那么这个状态就表示还剩多少个许可。ReentrantLock 是独占锁,这个状态通常表示:0(没锁)、1(已锁定)、>1(同个线程重入了多次)。

  • 底层实现:就是一个用 volatile 修饰的 int 变量,保证所有线程都能立刻看到它的最新值。所有抢号的行为最终都是通过 CAS 操作(一种乐观锁)来修改这个数字,保证不会多人同时改成功。


等待队列 (CLH Queue) - “排队区”
  • 通俗理解:当电子屏显示“0”,即没号了,后来的人怎么办?不能堵在诊室门口吧?保安会让他们去排队区坐着等。这个排队区就是一个 FIFO(先进先出)的队列。每个来排队的人会拿到一个排队号(Node)

  • 底层实现:AQS 内部维护了一个双向链表结构的队列。每个请求资源的线程都会被包装成一个 Node 节点,然后加入到这个队列的尾部。


核心工作流程:抢号与排队 (acquire)

现在,一个人(线程)想来挂专家号(获取资源),这套系统(AQS)的工作流程是这样的:

  1. 第一步:尝试直接抢号 (tryAcquire)

    • 他首先会看一眼大屏幕(读 State),如果还有号(state > 0),他就立刻尝试通过机器自助挂号(CAS 操作)。

    • 如果成功:他挂上号了,屏幕上的数字减一。他就可以直接进去看医生了(线程继续运行)。这个过程非常快,完全没有阻塞。

    • 如果失败:说明同时有另一个人和他一起抢,并且对方手更快。或者干脆就没号了(state = 0)。那么流程进入第二步。

  2. 第二步:入队等待 (addWaiter)

    • 抢号失败的人,会被系统(AQS)自动生成一张排队小票(Node节点),然后让他去排队区(等待队列) 的末尾坐着等。

  3. 第三步:排队并等待唤醒 (acquireQueued)

    • 坐在排队区的人并不会傻傻地一直问“到我了吗?到我了吗?”,这样会累死(消耗CPU)。

    • 他会睡觉(线程被挂起/LockSupport.park)

    • 那么谁叫他起床呢?当前面一个人看完病出来(释放资源)时,系统会叫排队区第一个位置的人起床(唤醒线程/LockSupport.unpark)。

    • 第一个位置的人被唤醒后,他会再次尝试去第一步抢号(因为可能同时有新的号放出来,或者他是公平锁条件下理所应当该他)。

    • 如果这次抢成功了,他就离开队列进去看医生。如果又失败了(比如突然来了个VIP?非公平锁下可能被插队),他会继续坐下睡觉,等待下一次被唤醒。

释放与唤醒 (release)

看完医生的人(线程)出来时,需要做一件事:

  1. 尝试释放号 (tryRelease):他会告诉系统:“我用完了”。系统会把大屏幕上的号数加一(修改 State)。

  2. 唤醒下一个 (unparkSuccessor):如果释放成功(比如锁完全解开了),系统(AQS)就会去排队区,找到第一个正在睡觉的人,拍拍他叫他起床(唤醒下一个线程),让他去尝试挂号。

总结:AQS形容的是一个流程,需要一个状态变量保证当前专家号是否可用,需要一个队列保证阻塞需要排队处理、第三个是工作流程在尝试CAS抢号操作、第四抢到看完病后就可以释放资源好和重新唤醒共享资源的状态

使用场景

  • 显式锁实现:如ReentrantLockReentrantReadWriteLock

  • 同步工具:如CountDownLatchSemaphoreCyclicBarrier

  • 自定义同步组件:如需要控制线程协作的场景。

使用方法

  1. 继承AQS:子类需实现tryAcquire(独占锁)或tryAcquireShared(共享锁)等方法。

  2. 调用模板方法:如acquire()release()等,AQS会自动处理队列管理。

示例代码(独占锁):

class Mutex extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {return compareAndSetState(0, 1); // CAS设置状态}@Overrideprotected boolean tryRelease(int arg) {setState(0); // 释放锁return true;}
}
// 使用
Mutex mutex = new Mutex();
mutex.acquire(1); // 加锁
mutex.release(1); // 解锁

用到的设计模式

  • 模板方法模式:AQS定义骨架(如acquire()),子类实现具体逻辑(如tryAcquire())。

  • 观察者模式:线程通过队列等待状态变化,类似观察者模式中的通知机制。

AQS中同步队列的数据结构

  • 双向链表(CLH队列):每个节点(Node)保存线程引用、等待状态(WAITINGCANCELLED等)、前驱和后继指针。

  • 状态变量(volatile int state):表示锁的占用情况(如ReentrantLock中state=0表示未占用)。

AQS原理

  1. 一个状态 (state):用来表示资源是否可用、可用多少。这是所有操作的核心。

  2. 一个队列:用来管理所有抢资源失败的线程,让它们排队,避免无序竞争。

  3. CAS + 自旋:在尝试获取资源时,使用高效的CAS操作,快速成功。

  4. Park/Unpark:对于抢失败的线程,不是忙等,而是让它休眠,由释放资源的线程来唤醒,极大节省了CPU资源。

核心伪代码逻辑:

// acquire流程
if (!tryAcquire(arg) && addWaiter(Node.EXCLUSIVE).acquireQueued())selfInterrupt();// release流程
if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);
}

文章转载自:

http://uGQswJYF.mwLxk.cn
http://7zvzbBvB.mwLxk.cn
http://j8alMG4q.mwLxk.cn
http://CQHpWS8q.mwLxk.cn
http://VoZOOaZk.mwLxk.cn
http://EQBVn4k5.mwLxk.cn
http://SGpVVDcx.mwLxk.cn
http://6Fx2u3ER.mwLxk.cn
http://jIZcjMuB.mwLxk.cn
http://RuQGI1bY.mwLxk.cn
http://2ytyK8uu.mwLxk.cn
http://r6iAGlIr.mwLxk.cn
http://K7LBhJLT.mwLxk.cn
http://5x8yEC4Q.mwLxk.cn
http://g2X8zalh.mwLxk.cn
http://hYPdGbGH.mwLxk.cn
http://Dfz4gdLe.mwLxk.cn
http://IVmKT9JF.mwLxk.cn
http://djgU69QE.mwLxk.cn
http://dbWqZkHD.mwLxk.cn
http://4VWcyG7J.mwLxk.cn
http://KNcntzT3.mwLxk.cn
http://VMvB4m3X.mwLxk.cn
http://CnFTxvWE.mwLxk.cn
http://BBNfrqki.mwLxk.cn
http://FGIM4MJj.mwLxk.cn
http://4gzkV0KW.mwLxk.cn
http://KHQAmGKy.mwLxk.cn
http://zakrnqhP.mwLxk.cn
http://0bReRPFy.mwLxk.cn
http://www.dtcms.com/a/379185.html

相关文章:

  • 电商 API 爬虫高阶技巧:多线程 / 异步请求结合,突破接口频率限制
  • vue两个组件互相引入时候会报错
  • 《芯片封装后未测试品粘连及边缘残胶的多维度工艺与材料失效分析》
  • MySQL基础全面解析
  • 探索容器技术:从LXC到Podman的演进
  • IntelliJ IDEA 启动项目时配置端口指南
  • java 实现rtsp 直播流下载
  • Python高级编程实战:装饰器、迭代器与生成器的深度应用
  • 高级SQL技术综合指南(MySQL)
  • 【51单片机】【protues仿真】基于51单片机电子琴系统
  • 解决idea2021maven依赖导入后还是找不到包,爆红无法导入
  • Netty学习
  • VGGNet:为什么16层简单堆叠能成为CNN经典?
  • 知识图谱RAG
  • 与controller层的接口入参注解@Valid有关的实体类判断空的注解
  • 基于AT89C52单片机的智能蓝牙台灯设计
  • Javaweb前端内容的思维导图
  • PyTorch深度学习实战【10】之神经网络的损失函数
  • 3.前置知识学习
  • Whois查询域名信息
  • 机器学习vs人类学习:人类学习如何借鉴机器学习方法?
  • ES6 面试题及详细答案 80题 (41-54)-- 异步编程(Promise/Generator/async)
  • Bug记录:Lombok @Builder 注解的两大陷阱及解决方案
  • ARM汇编 beep及bsp工程管理
  • 深入理解 Vue3 Router:三种路由模式的工作原理与实战应用
  • 2025 ICPC Gran Premio de Mexico 3ra Fecha
  • ZLMediaKit性能测试
  • 使用PyQt5和NumPy从TXT文件读取平面点集数据
  • nacos1.3.2 ARM 版容器镜像制作
  • LINUX中Docker Swarm的介绍和使用