- 请简述 Java 内存模型(JMM)的主要内容,以及它在多线程环境下是如何保证线程安全的?(全完不会)
答:Java 内存模型(JMM)定义了线程如何共享变量的规范,核心概念包括主内存和工作内存的划分,以及可见性和有序性。它通过volatile、synchronized等机制解决了线程之间的变量共享问题,确保线程安全。具体值包括:
(1)volatile:保证变量的可见性和禁止指令重新排序
(2)synchronized:提供代码块和方法的锁保护,保证原子性别和可见性
(3)原子类:通过CAS操作实现无所操作
(4)**Happens-Before机制**:定义操作的执行顺序,确保线程间正确的指令顺序
- HashMap 和 ConcurrentHashMap 的主要区别是什么?说一下 ConcurrentHashMap 的底层实现。
答:HashMap和ConcurrentHashMap的主要区别在于线程安全和并发优化方式
(1)线程安全性:HashMap是非线程安全的,ConcurrentHashMap是线程安全的。(在多线程并发的环境下HashMap会发生数据不一致以及环形链表的问题)
(2)**锁机制**:HashMap内部没有提供锁机制,ConcurrentHashMap通过分段式锁和CAS+Synchronized锁的方式实现线程安全(CAS保证插入数据时的线程安全性,锁是针对更细粒度比如链表和红黑树上的数据的更新进行的保护机制)
(3)数据模型:二者都采用数组+链表+红黑树的形式,但是ConcurrentHashMap针对并发进行了优化。
(4)**扩容机制**:HashMap单线程扩容,ConcurrentHashMap支持并发扩容
- Java 中的线程池如何管理线程?请简述 ThreadPoolExecutor 的核心参数及其含义。
答:Java的线程池通过ThreadPoolExecutor来管理线程的创建和销毁。它的核心参数包括:
(1)核心线程数:保持一定数量的线程始终活动
(2)最大线程数:允许的最大线程数,控制并发能力
(3)最大空闲时间:控制空闲线程的存活时间,避免资源的浪费
(4)阻塞队列:保存未处理的任务,支持有界队列和无界队列
(5)线程工厂:自定义线程的创建方式,可以支持线程的命名
(6)拒绝策略:处理任务溢出时的策略,包括直接丢弃,抛异常等
- 请解释 try-with-resources 的工作原理,以及它与传统 try-catch-finally 的区别。
答:Try-with-resource是Java提供的一种简化资源管理的结构,相较于传统的try-catch-finally:
(1)自动管理资源:资源会被自动释放,避免资源泄漏
(2)减少代码冗余:无需显示的书写finally块
(3)**更安全:即使发生异常,资源的close()方法仍会被正常调用,且异常抑制提升了调试的效率**
(4)**前提要求:资源必须实现 AutoCloseable 接口。**
- 请解释线程的生命周期及其各个状态之间的转换。
答:Java的线程周期包括New,Runnable,Running,Blocked,Waiting,Time-Waiting和Terminated其中状态。各个状态之间的转换由线程调度器,锁机制和显示的方法(notify/join)控制:
(1)Runnable和Running:Runnable是逻辑上的可运行状态,是否运行取决于CPU
(2)Blocked和Waiting:阻塞是因为锁未释放而挂起,等待是因显示调用而暂停
(3)Timed Waiting:等待有超时时间,到时候后自动恢复
线程中断不会自动进入就绪状态,需要结合InterruptedException和具体的实现逻辑处理。
- 请解释 JVM 垃圾回收机制的主要策略,以及 G1 垃圾收集器的核心原理。
答:
(1)JVM垃圾回收基于分代收集理论,结合标记-清除,复制算法,标记-整理算法
(2)G1垃圾回收通过分区管理,并发标记,增量整理和用户可控停顿时间,提升了低停顿,高性能的GC体验
(3)适用于大堆内存和延迟敏感的应用
- 实现一个线程安全的计数器类 SafeCounter
答:
(1)使用Redis Increment操作,实现分布式技术的原子性操作
(2)数据持久化,累计计数200次以后,通过异步的方式讲数据更新写入数据库。通过采用高性能内存队列disruptor,采用单生产多消费的模式,将数据写入数据库
如何在高并发环境下优化锁的性能?有哪些常见的锁优化方法?试举例说明。
答:(1)CAS机制 (2)多线程之间的通信技术 (3) 更高级的锁优化策略
- 为什么CAS能优化锁的性能?与传统锁(如 synchronized)的区别是什么?
(1)CAS(Compare-And-Swap)的关键在于通过硬件支持的原子性操作,在并发场景下避免了传统锁的 线程阻塞 和 上下文切换。
(2)原理:通过比较一个内存位置的当前值和预期值,仅当两者相等时,将内存位置的值更新为新值,这个过程是原子操作,由硬件层面(CPU指令)保证
(3)区别于传统锁:
传统锁:如 synchronized 或 ReentrantLock,依赖 JVM 或操作系统进行线程挂起和恢复,带来额外的**上下文切换**
CAS:无锁方案,无需挂起线程,直接在用户态完成操作,效率更高。
- CAS 存在哪些问题或缺陷?如何解决这些问题?
(1)ABA的问题:CAS 检测值是否发生变化,但不关心值变化的过程。 A-B-A,CAS无法做出检测任务没有变化
解决方案: 引入版本号/时间戳
(2)自旋消耗CPU:CAS 在并发冲突下会不断重试(自旋),高并发情况下可能导致 CPU 资源浪费
解决方案:限制自旋:设置最大自旋次数,超过则挂起线程
结合其他锁机制:在冲突频繁的情况下,降级为悲观锁(如 synchronized)
(3)只能保证单个数据的原子性:
解决方案:使用 AtomicReference:将多个变量封装成一个对象,通过 CAS 实现原子性操作
软件事务内存(STM):通过事务机制保证复杂操作的原子性。
- 在分布式系统中,如何保证事务的一致性?请列举至少三种实现方式,并说明它们的优缺点。
(1)两段式提交(2PC):
* 先进行预提交,等到协调者通知提交时(要么一同提交,要么一同回滚)
* 对分布式事务要求极高的业务
(2)TCC 模式:(抢票系统)
* Try:尝试执行,预留资源
* Confirm:确认提交
* Cancel:取消预留
(3)本地消息表(通过本地数据表 + 消息队列异步事务)(订单系统)
* 额外生成一张表记录事务信息
* 通过定时任务,不定时地向消息队列发送消息,知道消息完成
- 在高并发的场景中,除了 CAS 和线程通信,还有其他常见的锁优化策略锁分离(Lock Splitting),锁粗化(Lock Coarsening),读写锁(ReadWriteLock)解释使用场景
(1)锁分离,就是将一个大锁分离成多个小锁,从而减少锁的竞争,提高并发性能(例子ConcurrentHashMap的分段锁)
(2)锁粗化,就是将多个连续的锁合并成为一个更大的锁,以减少频繁加锁和解锁的开销。(连续操作同一资源,适合长生命周期任务)
(3)读写锁,读锁是共享锁,写锁是独占锁(Java提供了ReentrantReadWriteLock)
- 对于一个百万级别数据量的 MySQL 表,执行查询性能很慢,你会如何排查问题并优化?请详细说明具体操作步骤
(1) 分析 SQL 执行计划,优化索引和查询逻辑。
(2) 根据读写比例选择主从架构或分库分表策略。
(3) 优化数据库连接池配置,确保资源充足。
(4) 调整事务隔离级别,减少锁竞争。
(5) 引入缓存,降低数据库直接访问压力。
- 请解释什么是线程安全,以及如何在 Java 中实现线程安全?列举至少三种方式,并说明它们的应用场景。
(1) 线程安全就是在多线程的环境下,多个线程共同访问共享资源时必须保证原子性,一致性和可见性
(2) 实现线程安全方式有很多
a. 使用synchronized对方法/代码快进行加锁操作
b. 使用原子类工具,通过底层的CAS实现无锁操作
c. 使用Java并发包提供的方法,通过实现线程之间的通信,实现线程安全
d. 使用线程安全的集合
e. 使用 volatile 修饰符, 保证变量的可见性,即一个线程修改了变量的值,其他线程能够立即看到
场景 | 解决方案 | 备注 |
---|---|---|
多线程累加计数 | 原子类或 Synchronized | 原子类性能更优。 |
多线程资源池访问 | ReentrantLock 或信号量 | 保证资源的公平访问和控制。 |
实现线程关闭标志 | volatile | 简单高效,避免锁开销。 |
大量数据的并发读写 | ConcurrentHashMap | 替代传统集合,分段锁优化性能。 |
多线程任务调度(如多任务完成后汇总结果) | CountDownLatch 或 CyclicBarrier | 协调多线程任务的执行顺序。 |
- 假如一个线程在使用 Synchronized 时出现了死锁,你会如何排查和解决?
排查:
(1) 观察线程卡住的现象
* 应用响应变慢或无响应,线程池任务卡住。
* 查看日志,发现某些线程停留在临界区
(2) 使用线程转储(Thread Dump)分析
* 使用 jstack 工具获取线程转储:
* 分析转储文件,查找 BLOCKED 状态的线程,以及 waiting to lock 和 locked 关键词,找到哪些线程和对象导致了死锁
(3) 借助监控工具
* 使用 VisualVM、JConsole 或类似工具实时查看线程状态
* 在这些工具中,可以直接检测死锁线程及其持有和等待的锁。
(4) 添加日志
* 在加锁和释放锁的地方添加日志
解决:
(1) 规范化锁的获取顺序
(2) 使用tryLock(避免长时间等待)
(3) 使用更高层的并发工具(并发容器或者并发包)
* ConcurrentHashMap、BlockingQueue
* 信号量Semaphore或者读写锁ReentrantWriteReadLock
(4) 检查锁粒度
分布式系统的 CAP 理论
在分布式系统中要求满足一致性(C)、可用性(A)和分区容灾性(P),三者无法兼得,只能满足其他两个 (1)需要高可用性,即AP,需要完成最终一致同步 * 数据通过异步复制(如 Quorum 机制)在后台同步 * 应用场景包括电商、分布式缓存 (2)如果需要保证数据强一致性,即CP,可能会因为分区导致某些节点不可用,无法响应请求 (3)如果需要一致和可用性,只能在单机环境实现(单机数据库和数仓)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。