深入理解Java虚拟机-线程安全与锁优化

线程安全级别

不可变(Immutable)

对象状态不可变,天然线程安全。

String

Integer

绝对线程安全

所有操作都线程安全(Java 中极少见)。

Vector

(通过同步实现,但复合操作仍不安全)

相对线程安全

单次操作线程安全,复合操作需同步。

Collections.synchronizedList()

线程兼容

需调用方通过同步保证安全。

ArrayList

线程对立

无论是否同步都无法保证安全(设计错误)。

未正确同步的共享变量

线程安全的实现方式

互斥同步(Mutual Exclusion)

  • 核心机制:通过锁(如 synchronized、ReentrantLock)保证临界区代码的原子性
  • 底层原理:synchronized:基于对象头中的 Mark Word 和 Monitor 锁实现锁状态:无锁 → 偏向锁 → 轻量级锁 → 重量级锁ReentrantLock:基于 AQS(AbstractQueuedSynchronizer) 实现,支持公平/非公平锁

非阻塞同步(Non-Blocking)

  • 核心机制:通过 CAS(Compare-And-Swap) 实现无锁编程
  • CAS 指令AtomicIntegerAtomicReference 等类的底层实现
  • ABA 问题:通过 AtomicStampedReference 解决
  • 硬件支持:x86 架构的 cmpxchg 指令

锁优化策略

偏向锁(Biased Locking)

  • 目的:减少无竞争场景下的同步开销
  • 原理:记录首个获取锁的线程 ID,后续该线程无需 CAS 操作即可直接进入临界区
  • 适用场景:单线程重复访问锁
  • 参数-XX:+UseBiasedLocking(默认开启)

轻量级锁(Lightweight Locking)

  • 目的:减少多线程轻度竞争时的锁开销
  • 原理:通过 CAS 操作将对象头替换为线程栈指针,避免操作系统级阻塞
  • 适用场景:低并发竞争(如两个线程交替访问)

自旋锁(Spin Lock)

  • 目的:减少线程阻塞和唤醒的上下文切换开销
  • 原理:线程在竞争锁时循环等待(自旋),而非立即挂起
  • 参数-XX:PreBlockSpin=10(默认自旋 10 次后升级为阻塞)

锁消除(Lock Elimination)

  • 目的:去除无实际竞争场景下的冗余锁
  • 原理:JIT 编译器通过 逃逸分析,若发现锁对象仅被当前线程使用,直接删除锁操作

锁粗化(Lock Coarsening)

  • 目的:减少频繁加锁/解锁的开销
  • 原理:合并多个连续的锁操作为一个更大的锁范围

适应性自旋(Adaptive Spinning)

  • 目的:动态调整自旋策略,平衡 CPU 资源消耗
  • 原理:根据历史自旋成功率和锁持有时间,动态调整自旋次数或直接阻塞

重量级锁(Heavyweight Locking)

  • 目的:解决高并发竞争下的线程安全
  • 原理:依赖操作系统互斥量(mutex)和条件变量(condition variable),直接阻塞竞争线程
  • 适用场景:多线程高竞争(如秒杀场景)
全部评论

相关推荐

04-21 12:53
已编辑
广东药科大学 golang
树根科技的一面-半个小时算法题:删除有序链表的重复元素II原题LC82. 删除排序链表中的重复元素 II  是在牛客的核心代码模式一开始没运行成功后面说了思路 然后就放我过了我在最后说完思路之后调了一下代码之后运行成功完美😍就两道八股然后实习简单问了一下 没什么好回答的我操又是KPI我的面试八股:1,索引是什么?对于索引的理解?底层数据结构是什么我觉得这道题问的很经典但是可能大家回答的大差不差 这里希望大家看可以回答的有章法一点  我这里贴一下我的回答吧面试回答:索引是数据库中用于提高查询效率的数据结构。它类似于书籍中的目录、通过创建索引、数据库可以更快地定位到数据的位置,从而减少全表扫描的时间,显著提高查询性能。索引本质上是一个辅助数据结构、B+树索引是一种多路平衡树、常用于数据库的存储引擎。Innodb数据库默认使用 B+树作为索引。然后来介绍一下B+树索引1. 首先先说一下B+树的特点是什么:B+树的叶子节点存储数据、非叶子节点只存储索引、不存储实际数据、所有数据都集中在叶子节点。而且每个节点里的数据都是根据索引的值来顺序存放的 也就是顺序存储2. 然后呢B+树适合顺序访问及范围查询:B+树的叶子节点之间通过双向链表连接、每个节点都有指向前一个和后一个节点的指针、以及当前的数据。这种结构使得可以顺序访问、从任意一个叶子节点开始、通过双向指针快速地向前或向后访问其他节点访问高效、无需回溯即可遍历更多数据。也可以通过范围查询、可以通过在链表中找到该范围的起始叶子节点、然后顺序地访问链表中的节点。3. 以及最重要的是:B+树的查询性能比较高:由于B+树的结构是平衡的、所有叶子节点都在同一层、这意味着查询数据时、查询的深度通常比较浅(一般在3到4层)、即使数据量达到千万级、最多需要3~4次磁盘I/O操作就能找到目标数据。这极大减少了磁盘访问次数。 4. 还有就是B+树具有稳定的查询性能:由于B+树是平衡的,所有的数据检索操作都有相同的I/O延迟。这使得B+树特别适合用于存储大量数据并且要求稳定查询性能的场景、这也是我们选择B+树作为索引的主要原因  2.面试官问的问题:  面试官问的是:一个表下有多个索引,每一个索引下都是B+树结构、那每一个索引的叶子节点都是存储的实际的数据吗?一开始没明白这个是什么意思、后来面试官提示了一下才明白原来问我的是:聚簇索引和非聚簇索引(二级索引)的区别。参考回答:分为聚簇索引和二级索引回答聚簇索引:叶子节点:存储的是主键值和完整的行记录(即数据行的所有列数据)。非叶子节点:存储的是主键值和指向子节点的指针。非聚簇索引(二级索引):叶子节点:存储的是索引键值和该行的主键值、不存储完整数据。非叶子节点:存储的是索引键值和指向子节点的指针。查询过程:如果查询条件使用了二级索引(非聚簇索引)、但查询的数据既不是主键值也不是二级索引值、则需要通过二级索引查找主键值、然后回表查询实际数据。这样会进行两次B+树扫描、分别查找索引和数据。如果查询的列是主键值和二级索引值、并且这两列都在二级索引中(例如联合索引)、则可以直接在二级索引中查询到所有需要的字段、这时称为覆盖索引、无需回表、查询只需要扫描一次B+树、效率更高。
查看3道真题和解析
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客企业服务