写在前面
本文选题来源于各中大厂开发岗面试时的相关“八股”问题,全文干货无废话,适合具有一定Java语言基础的技术开发者阅读。
1. 按是否阻塞
(1)互斥锁(Mutex)
线程在获取锁失败时主动放弃CPU,进入阻塞状态,等待锁释放后被唤醒。Java的重量级锁依赖互斥锁实现(在下文中将会提到)。
互斥锁的阻塞与唤醒实现依赖操作系统的线程调度,需要消耗性能资源。高并发的情况下线程频繁阻塞与唤醒将影响程序性能。
(2)自旋锁(Spinlock)
线程在获取锁失败时循环重试(自旋),不放弃CPU不阻塞,直到锁可用。适用于锁持有时间极短(如计数器更新)、竞争不激烈的程序或核心数较多的系统。但如果长时间自旋,相当于让CPU“空转”,因此也会产生性能问题,Java为此设计了默认的自旋次数,自旋次数耗尽则锁升级,详情请见下文。
2. 按照锁的等级
(1)偏向锁(Biased Locking)
目的:优化单线程重复访问同步块的场景,避免无竞争时的同步操作。
实现:首次获取锁时,JVM在对象头中记录偏向线程ID。同一线程后续进入同步块(同步块可以简单理解为加锁的对象)时,直接检查线程ID是否匹配即可。
锁升级/锁膨胀(注意不是锁粗化,锁粗化是JVM对锁粒度的优化):当其他线程尝试获取锁时,偏向锁升级为轻量级锁。
(2)轻量级锁(Lightweight Locking)
目的:解决多线程交替访问同步块但无并发竞争的场景,避免产生线程阻塞(线程的阻塞和唤醒需要消耗性能资源)。
实现:线程通过CAS操作尝试将对象头中的标记替换为指向栈中锁记录的指针。若成功,线程获得锁;若失败(存在竞争),触发自旋锁循环重试。
锁升级:自旋次数超过默认次数(JDK 6后自适应调整)后或有多个线程(超过1个)在自旋等待同一个锁时,锁升级为重量级锁,防止因为自旋而过度消耗CPU。
(3)重量级锁(Heavyweight Locking)
目的:处理高并发竞争场景,确保线程互斥访问。
实现:基于操作系统的互斥量(Mutex)和条件变量(Condition Variable)。未获取锁的线程被阻塞,进入等待队列,由操作系统调度唤醒。
性能代价:涉及用户态与内核态切换,上下文开销大,高并发时性能显著下降。
需要注意的是,锁的等级在Java对象结构-对象头-Mark Word 中被明确指定。

图片来源:【Java并发】月薪30K必须知道的Java锁机制_哔哩哔哩_bilibili
3. 按照互斥程度(互斥锁的子类)
排它锁(写锁):获得锁的资源在被锁释放之前无法添加其他任何互斥锁;
共享锁(读锁):获得锁的资源允许被添加其他的共享锁,但无法添加排它锁。
4. 按照公平性
公平锁:需要手动开启,例如 ReentrantLock(true)。开启后将按线程请求锁的顺序分配锁(先到先得),并发性能较低,仅适用于需要严格指定顺序的业务逻辑,例如实时排名、交易订单等。
非公平锁:默认使用非公平锁,例如ReentrantLock()、ReentrantLock(false)、synchronized。性能和执行效率优先,允许线程插队获取锁(刚释放锁的线程可能直接再次获取)。
5. 按照机制(悲观锁和乐观锁)
悲观锁乐观锁在数据库中也有着广泛的应用,本文只讨论Java。
(1)悲观锁(Pessimistic Lock)
悲观锁在访问资源前先加锁。确保数据的强一致性。适用于对安全性和一致性要求高、写多读少、锁竞争激烈的场景。例如,银行转账、库存扣减等。
Java的synchronized关键字和ReentrantLock都是悲观锁。
(2)乐观锁(Optimistic Lock)
个人理解乐观锁更类似于一种机制,而不是一种真正的“锁”,有的时候会把乐观锁的运用也称为“无锁操作”。乐观锁允许直接操作数据,但在提交时检查是否发生冲突。冲突则重新执行。乐观锁通过检查前后数据/版本号是否一致,来确定是否发生冲突。适用于高并发、读多写少的场景。例如,点赞统计,状态标记等。
乐观锁的实现核心是CAS操作。CAS(Compare And Swap)是一种硬件级原子操作,通过比较比较内存值与预期值,若一致则更新为新值。CAS是Java原子类实现的基础。
6. 其他锁分类
可重入锁:允许某一线程多次获得锁。
可中断锁:等待时可中断。
超时获得锁:在规定时间内没有获得锁则放弃。
ReentrantLock实现了可中断锁、超时获得锁,synchronized和ReentrantLock都是可重入锁。
&spm=1001.2101.3001.5002&articleId=146071863&d=1&t=3&u=d4cda8d5f5f14f86bbbea859c046160e)
1539

被折叠的 条评论
为什么被折叠?



