并发

felix.shao2025-05-18JavaJava

并发

多线程

问题集锦-线程
 线程的创建方式有哪些?
  1. 继承 Thread 类

    • 通过继承并重写 run() 方法创建线程,适合简单任务。缺点:单继承限制灵活性(如 Java)。
  2. 实现接口(Runnable/Callable)

    • Runnable:解耦任务与线程管理,支持多线程共享同一任务对象。
    • Callable:支持返回值和异常处理,需配合 FutureTask 使用,适合异步结果场景。
    • C 语言的 pthread:使用 pthread_create 函数创建线程,需链接 pthread 库。
  3. 函数/任务传递(无需继承)

    • 直接传递函数或 Lambda 表达式(如 Python 的 target 参数、Java 的 Lambda)。
  4. 线程池管理

    • Java 的 ExecutorService:复用线程资源,控制线程数量和执行策略。
    • Python 的 ThreadPoolExecutor:自动管理线程生命周期,适合高并发场景。
  5. 其他高级方式

    • 协程:单线程内并发(如 Python asyncio),适合 I/O 密集型任务。
    • 守护线程:主线程结束时自动终止,适用于后台任务(如日志监控)。
    • 第三方库:如 Java 的 Akka(基于 Actor 模型)提供细粒度并发控制。
  6. 选择建议

    • 简单任务优先函数传递,需返回值用 Callable,高并发用线程池,跨平台/C 开发用 pthread。
 怎么启动线程 ?

 通过 Thread.start() 方法启动线程。

 如何停止一个线程的运行?
  1. 标志位控制退出

    • Java 使用 volatile 布尔变量作为退出标志,线程循环检查标志并主动退出。
    • Python 通过 threading.Event 或全局变量实现,支持安全退出和资源清理。
  2. 中断机制(Java 推荐)

    • 调用 Thread.interrupt() 设置中断标志,通过 isInterrupted() 检查状态。
    • 阻塞方法(如 sleep())触发 InterruptedException,需捕获并恢复中断标志。
  3. 线程池管理工具

    • Java 的 ExecutorService 通过 shutdown() 平缓关闭或 shutdownNow() 强制终止。
    • 使用 Future.cancel(true) 取消正在执行的任务。
  4. Python 特有方法

    • 守护线程随主线程结束自动终止,适合无需清理的后台任务。
    • concurrent.futures 模块支持通过 ThreadPoolExecutor 取消任务。
  5. 强制终止风险

    • Java 的 Thread.stop() 已废弃,可能导致资源泄漏或数据不一致。
    • Python 的 _stop() 非官方 API,存在安全隐患。
  6. 开发实践建议

    • Java 优先选择中断机制或标志位,Python 推荐 threading.Event
    • 处理阻塞操作时需恢复中断状态(如 try-catch 内调用 interrupt())。
    • 高并发场景使用线程池统一管理,避免直接操作线程。
 调用 interrupt 是如何让线程抛出异常的?
  1. 中断标志位触发机制

    • interrupt() 设置线程中断标志位(falsetrue),不会直接中断运行中的线程。
    • 阻塞方法(如 sleep()wait())内部自动检查标志位,若为 true 则抛出 InterruptedException
  2. 阻塞状态下的异常流程

    • 线程在 sleep() 等阻塞状态被中断时,JVM 会:
      • 清除中断标志位(重置为 false)。
      • 抛出 InterruptedException 使线程退出阻塞。
  3. 非阻塞状态行为差异

    • 线程运行时调用 interrupt() 仅设置标志位,需通过 isInterrupted() 主动检查并终止任务。
    • LockSupport.park() 等操作被中断时不抛异常,但保持标志位为 true,需手动检查。
  4. 中断标志位动态处理

    • 抛出 InterruptedException 的方法会重置标志位,捕获异常后需调用 interrupt() 重新标记中断状态。
  5. 底层实现原理

    • JVM 在阻塞方法中隐式插入中断检查逻辑,检测到标志位为 true 时触发本地方法抛异常,依赖线程调度器协作。
 Java 线程的状态有哪些?
  1. NEW 新建状态

    • 线程对象通过 new Thread() 创建后未调用 start(),未关联操作系统底层线程。
  2. RUNNABLE 可运行状态

    • 调用 start() 后进入此状态,包含就绪(等待 CPU 分配)和运行中(执行 run() 方法)两种子状态。
    • 操作系统层面的阻塞 I/O 在 Java 中仍归为 RUNNABLE。
  3. BLOCKED 阻塞状态

    • 竞争 synchronized 锁失败时进入此状态,进入监视器锁的阻塞队列,等待锁释放后被唤醒。
  4. WAITING 无限等待状态

    • 主动调用 Object.wait()Thread.join()LockSupport.park(),需其他线程显式唤醒(如 notify()interrupt())。
  5. TIMED_WAITING 超时等待状态

    • 调用带超时参数的方法如 Thread.sleep(long)Object.wait(long),超时或中断后自动恢复 RUNNABLE。
  6. TERMINATED 终止状态

    • run() 方法执行完毕或抛出未捕获异常后进入此状态,不可重启。
  7. 状态转换规则

    • NEW → RUNNABLE:调用 start()
    • RUNNABLE → BLOCKED:竞争 synchronized 锁失败。
    • RUNNABLE → WAITING:调用 wait()/join()
    • RUNNABLE → TIMED_WAITING:调用 sleep()/wait(long)
    • BLOCKED → RUNNABLE:成功获取锁。
    • WAITING/TIMED_WAITING → RUNNABLE:被唤醒、中断或超时。
 blocked 和 waiting 有啥区别
  1. 触发条件与锁资源行为

    • BLOCKED:被动触发(锁竞争失败),不释放已持有的锁。
    • WAITING:主动调用 wait()join()park(),释放当前对象锁。
  2. 唤醒机制

    • BLOCKED:锁被释放后自动唤醒,参与锁竞争。
    • WAITING:需显式唤醒(如 notify()unpark() 或目标线程终止)。
  3. 应用场景

    • BLOCKED:高并发锁竞争(如多线程访问共享资源)。
    • WAITING:线程协作(如生产者-消费者模型)。
  4. 状态转换

    • BLOCKED → RUNNABLE:成功获取锁后转换。
    • WAITING → RUNNABLE:唤醒后需重新获取锁,成功则进入 RUNNABLE,失败则转为 BLOCKED。
  5. JVM 管理队列

    • BLOCKED:同步队列(Entry Set)。
    • WAITING:等待队列(Wait Set)。

总结对比表

特性BLOCKEDWAITING
触发方式被动(锁竞争失败)主动(调用等待方法)
锁释放不释放锁(若未持有则不涉及)必须释放当前对象锁
唤醒机制自动(锁可用时)依赖外部显式操作(如 notify()
常见场景高并发锁竞争线程协作等待条件
JVM 管理队列同步队列(Entry Set)等待队列(Wait Set)
 wait 状态下的线程如何进行恢复到 running 状态?
  1. 显式唤醒

    • 其他线程在同步块内调用 notify()notifyAll(),唤醒一个或所有等待线程。
    • 被唤醒线程转为 BLOCKED 状态尝试重新获取锁,成功则进入 RUNNABLE
  2. 中断唤醒

    • 外部调用 interrupt() 使等待线程抛出 InterruptedException,需在 catch 块处理异常并调用 Thread.currentThread().interrupt() 恢复中断标志。
  3. 锁竞争与状态转换

    • 被唤醒线程必须重新竞争锁:
      • 成功获取锁 → RUNNABLE
      • 锁被占用 → BLOCKED 等待锁释放。
  4. 注意事项

    • wait()/notify() 必须在同步块内调用,否则抛出 IllegalMonitorStateException
    • 推荐用 while 循环检查条件,防止虚假唤醒(未被 notify 但被唤醒)。
    • LockSupport.park() 的线程需通过 unpark() 唤醒,无锁要求但需管理许可(permit)。
问题集锦-wait/notify 机制
 notify 和 notifyAll 的区别?
  1. 唤醒线程数量

    • notify() 随机唤醒一个等待线程。
    • notifyAll() 唤醒所有等待线程。
  2. 适用场景

    • notify() 适合单一任务场景(如生产者仅需唤醒一个消费者)。
    • notifyAll() 适合条件变化需全体响应的场景(如资源释放后通知所有消费者)。
  3. 性能与风险

    • notify() 性能开销小,但可能导致线程饥饿或条件未满足的线程被唤醒。
    • notifyAll() 安全性高,但会增加锁竞争和上下文切换开销。
  4. 锁竞争机制

    • notify() 唤醒的线程直接进入锁池竞争锁。
    • notifyAll() 唤醒的所有线程需先进入锁池竞争,只有获取锁的线程继续执行。
  5. 编码要求

    • 两者必须在 synchronized 块内调用,否则抛出 IllegalMonitorStateException
    • 被唤醒线程需用 while 循环检查条件,避免虚假唤醒。
    • 若无特殊需求,优先使用 notifyAll() 保证安全性。
 notify 选择哪个线程?
  1. JVM 规范与实现差异

    • 官方规范:notify() 唤醒的线程是任意的(arbitrary),具体选择由 JVM 实现决定。
    • HotSpot 实现:按等待队列的 FIFO 顺序唤醒第一个线程(如线程 A → B → C 等待时,先唤醒 A)。
  2. 实际唤醒机制

    • 等待队列结构:wait() 进入队列的线程按调用顺序排列,notify() 从队列头部选取线程唤醒。
    • 被唤醒线程需重新竞争锁,可能因锁被占用再次进入 BLOCKED 状态。
  3. 开发者应对策略

    • 避免依赖唤醒顺序:不同 JVM 实现可能不同,代码设计需假设唤醒顺序不可控。
    • 使用 while 循环检查条件:确保被唤醒后验证条件,避免虚假唤醒或非目标线程执行。
    • 明确唤醒目标:通过条件变量或多监视器对象间接控制唤醒逻辑,而非依赖 notify() 默认行为。
  4. notifyAll() 对比

    • notifyAll() 唤醒所有线程,但最终仅一个线程获取锁,适用于条件变化需全体响应的场景。
Java 里面的线程和操作系统的线程一样吗?
  1. 本质关系:1:1 映射实现

    • 现代 Java 线程(JDK 1.2+)基于操作系统原生线程实现(如 Linux 的 pthread),每个 Java 线程对应一个内核线程。
    • 通过 pthread_create 等系统调用创建内核线程,由操作系统调度器(如 Linux CFS)分配 CPU 时间片。例如 new Thread() 会触发 JVM 调用 pthread_create
  2. 核心区别:抽象层级与资源管理

    • 调度机制
      • Java 线程优先级(如 Thread.MAX_PRIORITY)会被映射到操作系统优先级(如 Linux 的 nice 值),映射规则因平台而异。
      • 操作系统可通过 sched_setscheduler() 设置实时调度策略(如 SCHED_FIFO),需 root 权限且影响系统稳定性。
    • 状态管理
      • Java 线程状态(RUNNABLEBLOCKED)是 JVM 逻辑状态,不完全对应内核线程的 readywaiting 状态。
    • 资源开销:Java 线程需额外 JVM 堆内存,上下文切换成本高于原生线程。
  3. 历史演变与新型线程

    • 绿色线程(JDK 1.2 前):用户态线程,JVM 自行调度,无法利用多核。
    • 虚拟线程(Java 19+):轻量级用户态线程,由 JVM 调度,多虚拟线程复用少量内核线程,适合高并发 I/O 场景。
  4. 开发注意事项

    • 线程数量控制:避免过度创建内核线程(受限于 ulimit -u),推荐使用 ThreadPoolExecutor
    • 线程类型选择
      • CPU 密集型任务:优先传统线程(1:1 模型)。
      • I/O 密集型任务:优先虚拟线程(减少上下文切换)。
    • 跨平台差异:Windows 支持 7 级优先级,Linux 优先级范围更窄,需谨慎依赖优先级逻辑。
使用多线程要注意哪些问题?
  1. 线程安全问题

    • 共享资源竞争:多线程同时修改共享变量(如计数器)会导致数据不一致。
      • 解决方案:使用 synchronizedLock 或原子类(如 AtomicInteger),或通过线程安全容器(如 ConcurrentHashMap)管理共享数据。
    • 不可变对象:优先设计不可变对象(如 String),避免同步开销。
  2. 死锁与活锁

    • 死锁条件:互斥、持有并等待、不可抢占、循环等待。
      • 预防:按固定顺序获取锁,使用 tryLock() 超时机制,避免嵌套锁。
    • 活锁:线程不断重试失败操作(如消息处理冲突),可通过随机退避策略解决。
  3. 同步机制的选择

    • synchronized:简单但粒度粗,适用于低竞争场景。
    • Lock/Condition:更灵活(如可中断、超时),适合高竞争或复杂条件等待。
    • CAS 无锁编程:通过 Atomic 类实现,适合高并发但低冲突场景(如计数器)。
  4. 资源管理与泄漏

    • 线程泄漏:未正确关闭线程池,导致线程持续占用资源。
      • 建议:使用 ExecutorService 并调用 shutdown(),避免手动创建线程。
    • 资源未释放:锁或 I/O 资源未在 finally 块中释放,导致死锁或泄漏。
  5. 性能与开销

    • 上下文切换成本:线程过多会导致 CPU 时间浪费在切换而非任务执行。
      • 优化:根据任务类型(CPU/I/O 密集)调整线程池大小,或使用协程(如虚拟线程)。
    • 伪共享(False Sharing):多个线程修改同一缓存行的不同变量,导致缓存失效。
      • 解决:使用 @Contended 注解或填充字节对齐变量。
  6. 内存可见性

    • volatile 关键字:确保变量修改对其他线程立即可见,但无法保证原子性。
    • happens-before 规则:通过同步操作(如 synchronizedLock)建立内存屏障,避免指令重排序问题。
  7. 异常处理

    • 未捕获异常:线程内未捕获的异常会导致线程终止且无日志。
      • 处理:通过 UncaughtExceptionHandler 统一捕获并记录异常。
  8. ThreadLocal 的使用与风险

    • 适用场景:保存线程私有数据(如数据库连接、用户会话)。
    • 内存泄漏:线程池中线程复用可能导致 ThreadLocal 数据长期未被清理。
      • 解决:使用后调用 remove() 方法清除数据。
  9. 并发工具的正确使用

    • 并发容器:优先使用 ConcurrentHashMapCopyOnWriteArrayList 代替同步容器(如 Hashtable)。
    • CountDownLatch/CyclicBarrier:协调多线程阶段性任务,避免忙等待。
  10. 平台与版本差异

    • 虚拟线程(Java 19+):适用于高并发 I/O 场景,但需注意与传统线程的交互。
    • 操作系统限制:Linux 默认线程数限制(ulimit -u),需调整参数支持大规模线程池。
保证数据的一致性有哪些方案呢?
  1. 事务型协议方案

    • 两阶段提交 (2PC):协调者与参与者分准备、提交两阶段交互,保证强一致性,但存在单点故障和性能瓶颈。
    • 三阶段提交 (3PC):增加预提交阶段,减少阻塞时间,复杂度更高。
    • TCC 模式:通过 Try-Confirm-Cancel 实现柔性事务,需手动补偿逻辑,适合复杂业务。
  2. 异步最终一致性方案

    • 消息队列:通过 Kafka、RabbitMQ 异步传递数据变更事件,结合重试机制实现最终一致性。
    • Saga 模式:拆分长事务为子事务,失败时触发补偿操作,适合订单、支付等链式场景。
  3. 锁与校验机制

    • 分布式锁:使用 Redis/Zookeeper 实现互斥访问,注意锁超时和死锁问题。
    • 数据版本控制:基于时间戳或序列号实现乐观锁(CAS),减少锁竞争。
    • 哈希校验:计算数据哈希值,用于快速验证一致性,适用于文件传输。
  4. 数据同步策略

    • 强一致性复制:采用 Paxos、Raft 协议确保多副本实时同步,适合金融等高要求场景。
    • 异步复制:通过日志同步降低延迟,容忍短期不一致,适合跨地域系统。
    • 双写策略:主备数据库同时写入,结合冲突检测(如 Last Write Wins)保障一致性。
  5. 新型技术方案

    • 区块链:基于共识算法(如 PoW/PoS)实现不可篡改账本,适合供应链、版权管理。
    • 云原生数据库:如 CockroachDB 支持多活架构和自动分片,内置强一致性。
    • 数据网格 (Data Mesh):通过领域自治和联邦治理实现跨平台一致性,需标准化接口。
  6. 实践建议

    • 强一致性选 2PC/TCC,最终一致性优先消息队列或 Saga。
    • 高并发场景避免锁竞争,采用无锁设计或异步补偿。
    • 结合监控告警和自动修复机制,降低不一致风险。
sleep 和 wait 的区别是什么?
  1. 所属类与方法类型

    • sleep()Thread 类的静态方法,可直接通过类名调用。
    • wait()Object 类的实例方法,必须在同步代码块或方法中调用。
  2. 锁的释放行为

    • sleep() 不释放持有的锁,其他线程无法进入同步代码块。
    • wait() 会释放当前对象的锁,允许其他线程获取锁执行同步代码。
  3. 唤醒机制与状态

    • sleep() 超时后自动恢复,线程进入 TIMED_WAITING 状态。
    • wait() 需通过 notify()/notifyAll() 或超时唤醒:
      • 无超时参数时进入 WAITING 状态。
      • 有超时参数时进入 TIMED_WAITING 状态。
  4. 使用场景与异常处理

    • sleep() 用于独立延时任务(如定时轮询),需处理 InterruptedException
    • wait() 用于多线程协作(如生产者-消费者模型),需处理 InterruptedExceptionIllegalMonitorStateException(未正确同步时抛出)。
  5. 参数与底层实现

    • sleep() 支持纳秒级精度参数(实际依赖操作系统调度)。
    • wait() 必须通过对象锁调用,支持超时参数(毫秒 + 纳秒)。
  6. 核心区别总结

特性sleep()wait()
锁释放❌ 不释放✅ 释放
调用位置任意位置同步块/方法内
唤醒方式自动恢复notify() 或超时
线程状态TIMED_WAITINGWAITING / TIMED_WAITING
适用场景独立延时任务多线程协作
sleep 会释放 cpu 吗?
  1. CPU 释放机制

    • Thread.sleep() 调用时线程主动让出 CPU 时间片,进入 TIMED_WAITING 状态,操作系统将其移出可运行队列,CPU 资源分配给其他就绪线程。
  2. 锁资源的保持

    • sleep() 不会释放已持有的锁(如 synchronized 同步块中的锁),其他线程无法获取同一锁。
  3. 性能影响与上下文切换

    • 短时间频繁调用 sleep()(如毫秒级)会导致高频线程切换,增加 CPU 调度开销。
    • 建议:高并发场景优先延长休眠时间(如 10ms 替代 1ms)或使用异步等待(如 CompletableFuture.delay())。
  4. 使用场景建议

    • 适用场景:独立延时任务(如定时轮询)、模拟耗时操作、调试。
    • 不适用场景:线程协作(需用 wait()/notify())、高精度实时任务(受操作系统调度影响)。
  5. 异常处理

    • 必须捕获 InterruptedException,并在捕获后通过 Thread.currentThread().interrupt() 恢复中断状态。
不同的线程之间如何通信?
  1. 等待/通知机制(wait()/notify()

    • 基于共享对象的监视器锁实现:线程调用 wait() 释放锁并等待,其他线程通过 notify()/notifyAll() 唤醒。
    • 适用场景:生产者-消费者模型等需要协调顺序的共享资源操作。
    • 注意:必须在 synchronized 代码块内使用,配合 while 循环防止虚假唤醒。
  2. 共享变量与 volatile

    • 通过共享变量传递数据,volatile 保证可见性但不保证原子性。
    • 适用场景:简单状态标志(如线程启停控制)。
  3. LockCondition

    • ReentrantLock 替代 synchronizedCondition 提供多条件队列支持,可精确控制唤醒顺序。
  4. 阻塞队列(BlockingQueue

    • 线程安全队列自动处理阻塞逻辑(如空队列阻塞消费者,满队列阻塞生产者)。
    • 实现类:ArrayBlockingQueue(固定容量)、LinkedBlockingQueue(可选容量)。
    • 适用场景:生产者-消费者模式的数据传递与同步。
  5. 并发工具类

    • CountDownLatch:主线程等待多个子线程完成任务(如初始化)。
    • CyclicBarrier:多线程相互等待至共同点后继续(可重用)。
    • Semaphore:控制资源并发访问数(如连接池)。
  6. 管道流(PipedInputStream/PipedOutputStream

    • 基于字节流的单向数据传输,适用简单数据传递场景,性能较低。
  7. 信号量与原子类

    • Semaphore 控制资源访问权限,支持公平/非公平策略。
    • AtomicInteger 等原子类实现无锁线程安全计数或状态管理。

并发安全

问题集锦-理论
 怎么理解可重入锁?
  1. 核心定义与特性

    • 允许同一线程多次获取同一把锁而不会死锁。
    • 重入计数器:每次获取锁时计数器递增,释放时递减,归零时锁完全释放。
    • 线程标识绑定:锁内部记录当前持有锁的线程,确保只有持有者能重入。
    • 避免递归死锁:线程在递归或嵌套方法中调用同一锁保护的代码时无需重复等待。
  2. 与不可重入锁的对比

    • 不可重入锁:同一线程重复获取锁时会阻塞,导致死锁(如自旋锁在递归中无限等待)。
    • 可重入锁优势:通过计数器机制安全重复进入同步区域,提升代码灵活性。
  3. Java 中的实现与应用

    • ReentrantLock
      • 显式锁,支持公平性选择(公平锁按请求顺序分配,非公平锁允许插队)。
      • 支持条件变量(Condition)实现精准线程唤醒(如生产者-消费者模型的队列分离)。
    • synchronized 关键字
      • 隐式可重入锁,功能有限(不支持中断或超时)。
  4. 典型应用场景

    • 递归算法:递归函数多次获取同一锁保护共享资源。
    • 同步嵌套调用:类中多个方法互斥访问共享资源且存在调用链。
    • 复杂线程协作:通过 Condition 实现多等待队列(如任务调度优先级控制)。
  5. 注意事项

    • 锁释放匹配:每次获取锁必须对应释放,否则计数器无法归零导致死锁。
    • 性能权衡:公平锁减少线程饥饿但增加切换开销,非公平锁反之。
    • 避免过度嵌套:深层嵌套增加调试复杂度(需追踪计数器状态)。
 多线程安全保障的核心策略
  1. 同步机制

    • 互斥锁
      • synchronized:自动管理锁,适用于简单临界区(如计数器操作)。
      • ReentrantLock:支持可中断锁、公平锁及条件变量,适合复杂同步需求(如超时控制)。
    • 读写锁
      • ReentrantReadWriteLock:读共享、写独占,提升读多写少场景性能(如缓存系统)。
  2. 无锁编程

    • 原子类
      • AtomicInteger/AtomicReference:基于 CAS 实现无锁线程安全操作(如高并发计数)。
    • 可见性控制
      • volatile:保证变量修改的可见性,适用于简单状态标志(需结合锁保证原子性)。
  3. 线程隔离与不可变设计

    • 线程局部存储
      • ThreadLocal:为每个线程提供独立变量副本(如会话管理),需调用 remove() 防泄漏。
    • 不可变对象
      • 对象状态不可修改(如 String、不可变集合),天然线程安全。
  4. 线程安全数据结构

    • 并发集合
      • ConcurrentHashMap:分段锁优化高并发读写,性能优于 HashTable
      • CopyOnWriteArrayList:写时复制机制,适合读多写少场景(如事件监听器列表)。
    • 阻塞队列
      • BlockingQueue:自动阻塞管理(如 LinkedBlockingQueue),简化生产者-消费者模型。
  5. 并发工具类

    • 任务协调工具
      • CountDownLatch:主线程等待多个子任务完成(如服务初始化)。
      • CyclicBarrier:多线程同步至共同阶段后继续执行(如分批次数据处理)。
    • 资源控制工具
      • Semaphore:控制资源并发访问量(如数据库连接池限流)。
  6. 执行框架类

    • 线程池管理
      • ThreadPoolExecutor:灵活配置线程池参数(核心线程数、队列策略等)。
    • 异步任务管理
      • FutureTask:封装异步计算结果,支持阻塞获取结果(如并行计算任务)。
  7. 锁与条件类

    • 高级锁机制
      • StampedLock:乐观读锁,减少读写锁竞争(读多写少场景优化)。
    • 条件变量
      • Condition:与 ReentrantLock 配合,实现细粒度等待/唤醒(如生产者-消费者模型)。
  8. 设计原则与优化

    • 锁粒度控制
      • 仅同步必要代码块(如共享变量操作),减少锁竞争时间。
    • 死锁预防
      • 统一锁顺序、设置超时(tryLock)、使用检测工具(如 jstack)。
    • 性能优化策略
      • 读多写少场景优先用无锁或乐观锁,高并发场景结合线程池管理。
 什么是公平锁和非公平锁?
  1. 公平锁

    • 获取顺序:严格按线程请求顺序分配锁,遵循“先到先得”原则,释放锁时队列中等待最久的线程优先获取。
    • 实现机制
      • 维护 FIFO 等待队列,通过 hasQueuedPredecessors() 检查队列状态。
      • 示例:Java 的 ReentrantLock(true) 在获取锁前检查队列。
    • 特点
      • 优点:避免线程饥饿。
      • 缺点:性能较低,因维护队列和线程切换开销增加。
  2. 非公平锁

    • 获取顺序:允许线程直接竞争锁,新请求线程可能插队抢占锁,无需检查队列。
    • 实现机制
      • 直接尝试 CAS 获取锁,失败后才加入队列。
      • 示例:Java 的 ReentrantLock() 默认模式。
    • 特点
      • 优点:吞吐量高,减少线程调度开销。
      • 缺点:可能导致线程饥饿。
  3. 核心区别

    • 锁分配顺序:公平锁严格按队列顺序,非公平锁允许插队。
    • 性能:非公平锁性能更高(减少线程切换),公平锁适合对公平性敏感的场景。
    • 实现复杂度:公平锁需维护队列状态,非公平锁实现更简单。
  4. Java 中的实现

    • ReentrantLock:通过构造函数参数 true 启用公平锁,默认或 false 为非公平锁。
    • synchronized:底层固定为非公平锁,不支持配置。
  5. 适用场景

    • 公平锁:需严格顺序的场景(如银行转账、任务调度)。
    • 非公平锁:高并发且锁竞争不激烈(如缓存读取、短任务处理)。
 非公平锁吞吐量为什么比公平锁大?
  1. 减少上下文切换开销

    • 非公平锁允许新线程直接抢占锁,避免公平锁强制唤醒队列头部线程的上下文切换(耗时 1-10 微秒)。
    • 刚释放锁的线程可能立即重新获取,减少线程状态切换次数。
  2. 利用 CPU 缓存局部性

    • 非公平锁允许刚释放锁的线程(缓存未失效)快速重入,避免公平锁切换线程导致的缓存重新加载(延迟 100-300 纳秒)。
  3. 避免队列维护成本

    • 非公平锁仅在竞争失败时加入队列,公平锁每次获取需检查队列状态(增加 20-50 纳秒/次开销)。
    • 实验数据:64 线程时非公平锁吞吐量下降幅度比公平锁低 60%-80%。
  4. 减少线程挂起概率

    • 锁持有时间短时,非公平锁有 30%-50% 概率让新线程直接获取锁,避免挂起和内核调度介入(耗时 1-5 微秒)。
  5. 优化系统调用频率

    • 非公平锁通过 CAS 自旋减少 park() 系统调用次数,相同并发量下系统调用比公平锁减少 40%-70%。
    • 高并发测试(64 线程)中吞吐量可达公平锁的 5-8 倍。
 什么情况会产生死锁问题?如何解决?
  1. 死锁产生的核心条件

    • 互斥条件:资源被独占使用(如数据库写锁)。
    • 请求与保持:持有资源的同时请求新资源(如线程A持有锁1后请求锁2)。
    • 不可剥夺:资源只能由持有者主动释放。
    • 循环等待:形成等待链(如线程A→B→C→A)。
  2. 典型死锁场景

    • 多线程嵌套锁:线程以不同顺序获取多个锁(如线程1锁A后锁B,线程2锁B后锁A)。
    • 数据库事务冲突:事务交叉更新记录(如事务T1更新X后请求Y,T2更新Y后请求X)。
    • 资源分配不合理:短任务高频竞争不可剥夺资源(如内存不足时进程互相等待)。
  3. 死锁解决方法

    • 预防策略
      • 统一资源申请顺序:按固定编号顺序获取锁(如先锁表A再锁表B)。
      • 一次性申请所有资源:启动前声明所需全部资源。
    • 动态避免
      • 银行家算法:动态检测资源分配安全性。
      • 超时机制:设置锁获取超时(如 tryLock(5, TimeUnit.SECONDS))。
    • 检测与恢复
      • 资源分配图检测:周期性扫描环路并终止部分进程。
      • 事务回滚:数据库自动回滚代价最小的事务。
    • 工程实践优化
      • 减少锁粒度:使用读写锁(如 ReentrantReadWriteLock)。
      • 无锁编程:采用 CAS(如 AtomicInteger)或线程本地存储。
  4. 不同场景的解决方案选择

    • 高并发服务:非公平锁+超时检测(如电商秒杀系统用 ReentrantLock)。
    • 金融交易系统:统一资源访问顺序+事务隔离级别(按账户ID升序更新)。
    • 实时操作系统:资源抢占机制(允许高优先级进程回收资源)。
问题集锦-synchronized
 synchronized 及其应用场景?
  1. 核心作用

    • 通过内置锁机制确保多线程环境下共享资源的原子性和可见性。
    • 基于对象监视器(Monitor)控制线程对同步代码的访问,同一时刻仅允许一个线程执行锁定代码。
  2. 三种使用方式

    • 修饰实例方法
      • 锁对象:当前实例(this)。
      • 场景:保护实例变量的线程安全操作,如多线程操作同一对象的计数器。
    • 修饰静态方法
      • 锁对象:类的 Class 对象(ClassName.class)。
      • 场景:保护静态变量的线程安全操作,如全局计数器或单例初始化。
    • 修饰代码块
      • 锁对象:可指定任意对象(如 this、自定义锁)。
      • 场景:缩小锁粒度提升性能,仅对关键代码段加锁。
  3. 典型应用场景

    • 共享资源的原子操作
      • 需保证复合操作完整性的场景,如计数器增减、转账操作、库存扣减。
    • 单例模式的双重检查锁定
      • 延迟初始化且保证线程安全,如全局配置类、数据库连接池。
    • 线程安全的数据结构
      • 保护集合类的并发修改,如 Collections.synchronizedList 包装的 ArrayList
    • 生产者-消费者模型
      • 协调线程间的数据传递,如消息队列的任务生产和消费。
  4. 注意事项

    • 锁粒度控制
      • 避免全方法加锁,优先同步关键代码段以减少竞争。
    • 死锁预防
      • 统一多锁的获取顺序(如按对象哈希值排序),避免循环等待。
    • 性能优化
      • 减少锁持有时间,利用 JVM 锁升级机制(偏向锁→轻量级锁→重量级锁)。
    • 异常处理
      • finally 块中释放锁,防止线程阻塞导致资源泄漏。
 synchronized 是公平锁吗?
  1. synchronized 的公平性本质

    • 底层实现默认且固定为非公平锁,不保证线程按请求顺序获取锁。
    • 新请求线程可能直接抢占锁,而非遵循队列顺序。
  2. 非公平锁的核心表现

    • 随机竞争机制:锁释放时 JVM 不检查队列顺序,新线程与队列线程随机竞争。
    • 潜在饥饿风险:高频短任务场景中可能出现线程长期无法获取锁。
  3. 设计选择的原因

    • 性能优先:减少线程唤醒和上下文切换(每次 1-10 微秒),吞吐量可达公平锁的 5-8 倍。
    • 实现简化:锁升级机制(偏向锁→轻量级锁→重量级锁)针对非公平场景优化。
  4. 与 ReentrantLock 的对比

    • 灵活性差异:ReentrantLock 可配置公平/非公平模式,synchronized 仅支持非公平。
    • 适用场景
      • synchronized:锁持有时间短、竞争不激烈(如计数器递增)。
      • ReentrantLock 公平模式:需严格顺序的场景(如银行转账)。
 synchronized 锁静态方法和普通方法区别?
  1. 锁对象不同

    • 普通方法:锁定当前实例对象(this),同一实例的同步普通方法互斥,不同实例方法可并行执行。
    • 静态方法:锁定类的 Class 对象,所有实例调用同步静态方法均互斥,全局仅一个线程可执行。
  2. 作用范围差异

    • 普通方法:仅对同一对象实例的同步方法互斥,不同实例的同名方法不受影响。
    • 静态方法:同一时间任何实例的同步静态方法均不可并行执行。
  3. 锁的粒度

    • 普通方法:锁粒度较细,影响单个实例操作,适合高频实例级资源保护。
    • 静态方法:锁粒度较粗,全局锁定类级别资源,可能成为性能瓶颈。
  4. 适用场景

    • 普通方法:保护实例变量(如对象属性修改),避免多线程数据竞争。
    • 静态方法:保护静态变量或实现类级同步逻辑(如单例模式双重检查锁定)。
  5. 线程交互影响

    • 普通方法:不同线程访问不同实例的同步普通方法可并发执行。
    • 静态方法:同一时间仅一个线程能执行静态同步方法(如全局配置更新)。
  6. 锁类型独立性

    • 互不干扰:类锁与对象锁可同时持有(如线程持有类锁后仍可获取对象锁)。
    • 锁升级机制:JVM 对两种锁均可能进行偏向锁、轻量级锁、重量级锁优化升级。
 synchronized 支持重入吗?如何实现的?
  1. synchronized 支持重入

    • 同一线程可多次获取同一对象的锁而不会阻塞,避免递归或嵌套调用导致的死锁。
  2. 实现原理

    • 锁计数器与监视器机制
      • 对象关联锁计数器(status)和持有线程 ID,首次获取计数器置 1,重入时递增,释放时递减,归零后完全释放。
    • 字节码指令支持
      • 通过 monitorentermonitorexit 指令控制代码块进入/退出时的锁状态检查与计数器更新。
    • 锁升级机制
      • 无竞争时使用偏向锁记录线程 ID,竞争升级为轻量级锁(CAS 自旋),激烈竞争时转为重量级锁(操作系统互斥锁)。
  3. 优势与局限

    • 优势
      • 自动管理锁释放,避免死锁风险。
      • 支持递归调用和嵌套同步代码块,简化编程逻辑。
    • 局限
      • 高并发场景下频繁锁升级(如偏向锁→重量级锁)可能带来性能损耗。
  4. 与显式锁的对比

    • synchronized 的可重入性是隐式自动管理,而 ReentrantLock 需手动管理计数器。
 syncronized 锁升级的过程讲一下
  1. 无锁状态

    • 对象创建时无锁,Mark Word 无锁标记,所有线程可自由访问。
  2. 偏向锁阶段

    • 触发条件:第一个线程首次获取锁时,JVM 将对象头 Mark Word 设为偏向锁状态(锁标志位 01),记录线程 ID。
    • 优化目的:单线程重复获取锁时无需同步操作,直接通过线程 ID 比对进入同步代码块。
    • 撤销机制:其他线程尝试获取锁时,JVM 暂停持有线程,若锁已释放则升级为轻量级锁。
  3. 轻量级锁阶段

    • 触发条件:多线程低竞争时升级(锁标志位 00)。
    • 实现方式
      • 线程栈帧创建锁记录(Lock Record),拷贝对象头 Mark Word。
      • 通过 CAS 替换对象头为指向锁记录的指针,成功则获取锁,失败则自旋等待。
    • 自旋优化:自适应策略动态调整自旋次数以减少 CPU 空转。
  4. 重量级锁阶段

    • 触发条件:自旋次数超限或竞争激烈时升级(锁标志位 10)。
    • 实现机制
      • 依赖操作系统互斥量(Mutex)实现阻塞与唤醒,未获锁线程进入等待队列。
      • 涉及用户态到内核态切换,带来高上下文切换开销。
    • 不可逆性:升级后无法降级,即使竞争减弱仍保持该状态。
  5. 锁升级的意义

    • 性能优化:减少低竞争场景同步开销(如偏向锁单线程优化)。
    • 平衡机制:轻量级锁减少阻塞,重量级锁保障高并发有序执行。
    • Mark Word 动态变化:对象头存储不同元数据(线程 ID、锁记录指针等)。

 注意高版本因为偏向锁性能收益低和实现过于复杂,已将偏向锁移除。

 JVM 对 synchornized 的优化?
  1. 锁升级机制

    • 轻量级锁
      • 通过 CAS 操作和线程栈的锁记录实现非阻塞同步,首次获取时拷贝对象头 Mark Word 至锁记录,CAS 替换对象头指针。
      • 自适应自旋策略动态调整自旋次数,减少 CPU 空转。
    • 重量级锁
      • 高竞争时升级为操作系统互斥锁(Mutex),依赖等待队列管理线程阻塞与唤醒。
      • 用户态到内核态切换带来高开销,升级后不可逆。
  2. 锁消除

    • JIT 编译器通过逃逸分析检测同步对象是否仅限当前线程使用,未逃逸时消除锁操作。
    • 典型场景:未逃逸的局部变量(如方法内的 StringBuffer)同步被优化为无锁形式。
  3. 锁粗化

    • 合并相邻同步代码块减少加锁/解锁开销,如循环体内多次同步合并为单次同步。
    • 优化原则:临界区执行时间需远小于线程调度时间片(通常控制在 1 毫秒内)。
  4. 自适应锁优化

    • 动态选择锁策略:低竞争时自旋等待,高竞争时直接阻塞线程。
    • 自旋次数阈值基于历史竞争数据动态调整,平衡 CPU 利用率与响应速度。
  5. 偏向锁替代方案

    • 现代 JVM(JDK 15+)移除偏向锁,优先采用锁消除和轻量级锁优化。
    • 遗留支持:通过 -XX:+UseBiasedLocking 强制启用(官方不推荐)。
  6. 其他优化策略

    • Mark Word 复用:对象头动态存储线程 ID、锁记录指针等元数据。
    • 可重入性:通过锁计数器实现同一线程多次获取锁,避免递归死锁。
    • 锁粒度控制:低竞争用细粒度锁,高竞争改用显式锁(如 ReentrantLock)。
问题集锦-ReentrantLock
 ReentrantLock 及其应用场景?
  1. 核心特性

    • 可重入性:同一线程可多次获取同一锁,避免递归调用或嵌套同步块导致的死锁。
    • 公平性选择:支持公平锁(按请求顺序分配)和非公平锁(默认模式),前者减少线程饥饿但吞吐量较低。
    • 条件变量:通过 newCondition() 创建多个等待队列,实现细粒度线程协作(如生产者-消费者模型的不同唤醒条件)。
    • 锁超时与中断响应:提供 tryLock(timeout, unit)lockInterruptibly(),支持超时获取锁和中断等待。
    • 锁状态监控:通过 getHoldCount() 查询锁重入次数,isLocked() 判断锁是否被占用。
  2. 应用场景

    • 高竞争环境下的共享资源保护
      • 多线程操作计数器、转账逻辑等需要原子性更新的场景,相比 synchronized 在高并发下性能更优。
    • 需要公平调度的系统
      • 任务调度需按提交顺序处理请求的场景(如金融交易系统)。
    • 复杂线程协作模型
      • 生产者-消费者模型中结合 Condition 实现满队列和空队列的精准唤醒。
      • 阻塞队列通过 tryLock 控制入队/出队的超时逻辑。
    • 框架级并发控制
      • 保护线程池状态(如 ThreadPoolExecutorRUNNING 状态)和任务队列的线程安全。
      • ConcurrentHashMap 的分段锁机制中使用 ReentrantLock 控制哈希桶的并发访问。
    • 需要锁中断或超时响应的场景
      • 分布式锁实现中通过 tryLock 避免线程因网络问题长期阻塞。
      • 实时系统要求任务在指定时间内完成并执行降级策略。
  3. 注意事项

    • 手动释放锁:必须在 finally 块中调用 unlock(),否则可能导致死锁或资源泄漏。
    • 避免锁嵌套:支持可重入但过度嵌套会增加锁竞争复杂度。
    • 公平锁的权衡:仅在严格顺序需求时使用,避免增加线程切换开销。
    • 性能监控:通过 getQueueLength() 监控等待线程数,优化锁竞争热点。
 ReentrantLock 是怎么实现公平锁的?
  1. 公平锁的核心实现机制

    • FIFO 队列顺序:通过 FairSync 类实现,线程调用 lock() 时必须先检查 AQS 队列是否存在等待线程。
      • 队列为空或无前驱节点时尝试 CAS 获取锁。
      • 队列存在等待线程时,当前线程加入队列尾部排队。
  2. 关键方法 hasQueuedPredecessors()

    • tryAcquire() 中调用该方法检查队列状态,确保只有队列头部线程能获取锁,阻止新线程插队。
  3. CLH 队列管理

    • AQS 维护 CLH 双向链表队列,公平锁通过 enq() 将竞争失败的线程封装为 Node 节点加入队列尾部。
    • 锁释放时唤醒队列头部节点的线程,保证顺序执行。
  4. 与非公平锁的差异

    • 初始抢占逻辑
      • 公平锁:必须检查队列状态后才尝试 CAS。
      • 非公平锁:直接 CAS 抢占,不检查队列。
    • 性能表现
      • 公平锁吞吐量较低(约下降 5-8 倍),适合顺序敏感场景。
      • 非公平锁适合短任务竞争,吞吐量更高。
    • 线程饥饿风险
      • 公平锁无饥饿问题,非公平锁可能引发线程长期等待。
  5. 适用场景与代价

    • 适用场景:支付系统、任务调度等需严格顺序的场景。
    • 实现代价
      • 每次锁获取需队列检查(增加 20-50 纳秒开销)。
      • 频繁线程切换导致性能下降(对比非公平锁吞吐量降低 60%-80%)。
问题集锦-同步工具
 CountDownLatch 是做什么的讲一讲?
  1. 核心功能

    • 允许一个或多个线程等待其他线程完成特定操作后再继续执行。
    • 内部维护计数器,初始化时指定等待的线程数量,countDown() 减少计数,计数器归零时唤醒等待线程。
  2. 工作原理

    • 初始化计数器new CountDownLatch(n) 设置初始值 n(需等待的任务数)。
    • 任务完成通知:子线程调用 countDown() 安全减少计数(基于 CAS 实现)。
    • 阻塞与唤醒:主线程 await() 阻塞至计数器归零,支持超时 await(timeout, unit)
  3. 典型应用场景

    • 主线程等待子任务完成:服务启动时等待所有组件初始化完毕。
      CountDownLatch latch = new CountDownLatch(3);  
      // 启动 3 个子线程初始化服务并调用 latch.countDown()  
      latch.await(); // 阻塞至所有服务就绪  
      
    • 多线程结果汇总:并行计算后主线程汇总结果(如分片数据处理)。
    • 并发测试同步:确保所有并发任务执行完毕后再验证结果。
    • 资源协调:学生全部离场后通知家长,顾客到齐后服务员上菜。
  4. 注意事项

    • 一次性使用:计数器归零后无法重置,需重新创建。
    • 异常处理:子线程在 finally 中调用 countDown() 避免主线程永久阻塞。
    • 超时机制:建议设置 await() 超时防止系统僵死。
    • 精确计数:初始值与实际任务数严格匹配(过多导致阻塞,过少提前唤醒)。
问题集锦-AQS
 介绍一下 AQS
  1. AQS 的基本概念与核心功能

    • 定义:Java 并发包(JUC)的核心框架,用于构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch)。
    • 核心功能
      • 通过 volatile int state 管理资源状态,支持原子操作(getState()/setState()/CAS)。
      • 基于 CLH 队列实现线程排队与唤醒,通过 LockSupport 实现线程阻塞(park())与唤醒(unpark())。
  2. 底层实现原理

    • 同步队列(CLH 队列)
      • 结构:双向链表,节点(Node)包含线程、等待状态(waitStatus)及前后指针。
      • 作用:存储未获取资源的线程,实现 FIFO 公平调度,头节点(head)表示当前持有资源的线程。
    • 条件队列
      • 单向链表,用于 Condition 的等待/通知机制(如 await()signal())。
  3. 资源共享模式

    • 独占模式
      • 同一时刻仅一个线程访问资源(如 ReentrantLock),通过 tryAcquire()/tryRelease() 实现。
      • 可重入性:state 记录同一线程多次获取锁的计数。
    • 共享模式
      • 允许多线程同时访问资源(如 Semaphore、CountDownLatch),通过 tryAcquireShared()/tryReleaseShared() 实现。
  4. 典型应用场景

    • 锁实现:ReentrantLock 重写 tryAcquire() 实现公平/非公平锁。
    • 同步器
      • CountDownLatchstate 初始化为计数值,countDown() 递减,归零时唤醒所有等待线程。
      • Semaphore:通过 state 控制并发线程数,acquire()/release() 操作许可。
  5. 优势与设计特点

    • 模板方法模式:子类仅需实现 tryAcquire()/tryRelease() 等模板方法,AQS 自动处理队列管理与调度。
    • 性能优化
      • 双向链表支持快速删除节点(如线程超时或中断时)。
      • 自旋优化减少上下文切换开销。
    • 可扩展性:通过组合 AQS 实现复杂同步策略(如读写锁分离)。
 如何用 AQS 实现一个可重入的公平锁?
  1. 核心实现原理

    • 公平性保证
      • 通过 AQS 的 CLH 队列实现线程 FIFO 排队,在 tryAcquire() 中调用 hasQueuedPredecessors() 检查队列是否有等待线程。
    • 可重入性
      • 通过 state 变量记录锁持有次数,同一线程多次获取时递增 state,释放时递减至归零。
  2. 关键实现步骤

    • 定义 Sync 类继承 AQS
      • 创建 ReentrantFairLock 类,内部定义 Sync 类继承 AbstractQueuedSynchronizer
    • 重写 tryAcquire 方法
      • 检查 state == 0:若无等待线程且 CAS 设置 state 成功,获取锁并设置独占线程。
      • 若当前线程是持有者,state += acquires 实现可重入。
    • 重写 tryRelease 方法
      • 递减 state,归零时清空独占线程标识。
  3. 公平锁与非公平锁区别

    • 公平锁:强制检查队列状态,仅无等待线程时尝试获取锁。
    • 非公平锁:允许直接 CAS 抢占锁,无需检查队列。
  4. 锁操作流程

    • 加锁
      • lock() 调用失败时,线程进入 CLH 队列并阻塞,等待前驱节点唤醒。
    • 解锁
      • unlock() 释放锁后,唤醒队列中下一个线程。
  5. 性能与注意事项

    • 公平性代价:获取锁需遍历队列,吞吐量低于非公平锁。
    • 重入限制lock()unlock() 次数需严格匹配,否则导致死锁或状态异常。
问题集锦-悲观锁、乐观锁
 悲观锁和乐观锁的区别?
  1. 核心思想差异

    • 悲观锁
      • 假设并发冲突必然发生,操作数据前必须先加锁(如数据库行锁、表锁),确保独占访问。
    • 乐观锁
      • 假设并发冲突极少发生,仅在数据更新时检测是否被修改(通过版本号或 CAS),允许无锁并发读取。
  2. 实现机制对比

    • 悲观锁
      • 加锁时机:操作数据前加锁(如 SELECT FOR UPDATE)。
      • 典型实现:数据库行锁、Java synchronized
      • 冲突处理:通过阻塞其他线程避免冲突。
      • 数据一致性:强一致性(全程独占)。
    • 乐观锁
      • 加锁时机:仅在更新时检测冲突(无前置锁)。
      • 典型实现:版本号机制、CAS 算法(如 AtomicInteger)。
      • 冲突处理:检测到冲突后回滚或重试。
      • 数据一致性:最终一致性(允许临时中间状态)。
  3. 性能与适用场景

    • 悲观锁适用场景
      • 高并发写操作(如金融转账、库存扣减)。
      • 需严格避免脏读、不可重复读的场景。
      • 缺点:锁竞争导致线程阻塞,吞吐量低。
    • 乐观锁适用场景
      • 高并发读操作(如新闻浏览、社交点赞)。
      • 冲突概率低的场景(如用户信息更新)。
      • 缺点:频繁冲突时重试开销大(需配合重试策略)。
  4. 典型问题与解决方案

    • 悲观锁死锁
      • 解决方案:锁超时机制(如 MySQL 的 innodb_lock_wait_timeout)、死锁检测算法。
    • 乐观锁 ABA 问题
      • 解决方案:带版本号的 CAS(如 AtomicStampedReference)。
  5. 混合策略实践

    • 分段锁(如 ConcurrentHashMap):将全局锁拆分为多个细粒度锁,平衡性能与一致性。
    • 自适应锁(如 Java 锁升级):根据竞争动态切换悲观/乐观策略。
 Java 中想实现一个乐观锁,都有哪些方式?
  1. 基于版本号机制

    • 数据对象增加版本号字段(如 version),更新时校验当前版本号是否一致,一致则递增版本并更新,否则重试或回滚。
    • Java 实现:通过 AtomicInteger 管理版本号字段,结合业务逻辑校验。
  2. 基于时间戳机制

    • 使用时间戳字段替代版本号,更新时校验时间戳是否一致。
    • 注意点:需处理分布式系统时钟同步问题,避免因时间误差导致冲突。
  3. CAS 原子操作

    • 使用 AtomicIntegerAtomicLong 等原子类,依赖 CPU 的 cmpxchg 指令实现原子性比较与交换。
    • 扩展方案
      • 解决 ABA 问题:使用 AtomicStampedReference 附加版本号或时间戳。
  4. JPA/Hibernate 的 @Version 注解

    • 在实体类中添加 @Version 注解字段,框架自动管理版本号校验,简化数据库乐观锁实现。
  5. Redis 分布式乐观锁

    • 利用 Redis 的 SETNX(setIfAbsent) 实现跨进程锁,结合超时机制避免死锁。
    • 典型场景:分布式系统数据同步、秒杀库存控制。

适用场景建议

  • 低冲突场景:优先使用版本号或 CAS 减少锁开销。
  • 高并发写操作:结合数据库版本号机制确保强一致性。
  • 分布式系统:采用 Redis 锁实现跨服务同步。
  • ORM 框架集成:直接使用 @Version 简化开发。

注意事项

  • ABA 问题:优先使用 AtomicStampedReference 替代基础 CAS。
  • 自旋开销:设置最大重试次数,避免 CPU 资源浪费。
  • 性能权衡:高冲突场景评估乐观锁重试成本与悲观锁阻塞开销。
 CAS 有什么缺点?
  1. ABA 问题

    • 问题描述:CAS 仅检测值是否变化,无法感知中间状态变化(如 A→B→A)。
    • 解决方案:引入版本号机制(如 AtomicStampedReference 类),同时比对值和版本号。
  2. 自旋开销大

    • 问题描述:高并发场景下,CAS 失败后循环重试导致 CPU 资源浪费。
    • 解决方案
      • 设置最大自旋次数或退避策略(如 Thread.yield())。
      • 高竞争场景改用锁机制(如 synchronized)。
  3. 仅支持单变量原子性

    • 问题描述:无法原子操作多个共享变量,可能导致中间状态不一致。
    • 解决方案
      • 封装多个变量为对象,通过 AtomicReference 管理。
      • 使用锁机制或事务内存(如 JDK9+ 的 VarHandle)。
  4. 高竞争性能下降

    • 问题描述:线程竞争激烈时,CAS 成功率低导致吞吐量下降。
    • 解决方案:改用分段计数机制(如 LongAdder 替代 AtomicLong)。
  5. 其他潜在问题

    • 优先级反转:高优先级线程自旋等待低优先级线程释放资源。
    • 硬件依赖:部分系统需依赖锁或其他同步机制。
    • 代码复杂性:无锁数据结构实现需处理边界条件,开发难度高。
 为什么不能所有的锁都用 CAS?
  1. 高竞争环境性能瓶颈

    • 自旋开销大:CAS 失败后持续重试导致 CPU 资源浪费,高并发场景(如秒杀系统)下传统锁的阻塞机制更高效。
    • 适应性不足:CAS 无法动态调整等待策略,传统锁(如 ReentrantLock)可结合公平锁优化线程调度。
  2. 功能局限性

    • 仅支持单变量原子操作:无法处理多变量或复合操作(如转账需同时修改两个账户余额)。
    • 缺乏高级同步支持:无法实现条件变量(Condition)、读写锁分离等功能。
  3. ABA 问题与维护成本

    • 值变化不可感知:A→B→A 的变化导致逻辑错误(如无锁栈顶元素多次弹出后重新压入)。
    • 解决方案复杂:需引入版本号(AtomicStampedReference)或时间戳,增加代码复杂度。
  4. 线程公平性与资源分配

    • 饥饿风险:CAS 不保证线程排队顺序,公平锁通过 CLH 队列强制 FIFO 避免线程饿死。
    • 优先级反转:高优先级线程可能因自旋等待低优先级线程被阻塞。
  5. 硬件与场景依赖

    • 硬件限制:部分嵌入式系统缺乏 CAS 指令支持,需依赖锁机制。
    • 适用场景局限:长耗时操作(如数据库事务)用锁更合适,CAS 适合短临界区、低冲突场景。
  6. 替代方案与混合策略

    • 优先 CAS 场景:简单原子操作(如计数器自增)、低冲突环境(状态标志位更新)。
    • 改用锁机制场景:复杂业务逻辑(多变量事务)、需公平性保障(任务队列调度)。
    • 混合策略:分段计数(LongAdder)、乐观读模式(StampedLock)。
 CAS 有什么问题,Java 是怎么解决的?
  1. ABA 问题

    • 问题:无法感知中间状态变化(如 A→B→A)。
    • Java 解决方案:通过 AtomicStampedReference 引入版本号,同时比对值和版本号。
  2. 自旋开销大

    • 问题:高并发下循环重试导致 CPU 浪费。
    • Java 解决方案
      • 分段 CAS 机制:如 LongAdder 拆分计数器减少竞争。
      • 退避策略:结合 Thread.yield() 或限制自旋次数。
  3. 仅支持单变量原子操作

    • 问题:无法原子操作多变量导致中间状态不一致。
    • Java 解决方案
      • 封装复合状态:使用 AtomicReference 管理对象。
      • 事务内存:JDK9+ 的 VarHandle 支持多变量原子操作。
  4. 高竞争性能下降

    • 问题:线程竞争激烈时吞吐量降低。
    • Java 解决方案
      • 切换锁机制:如 synchronizedReentrantLock
      • 混合策略:AQS 框架结合 CAS 与队列机制。
  5. 硬件依赖与代码复杂性

    • 问题:部分系统缺乏 CAS 指令支持,无锁数据结构开发复杂。
    • Java 解决方案
      • 内置原子类:如 AtomicInteger 屏蔽底层差异。
      • 工具类封装:提供 ConcurrentLinkedQueue 等线程安全结构。
问题集锦-voliatle
 voliatle 关键字有什么作用?
  1. 保证内存可见性

    • 强制每次读写操作直接访问主内存,确保多线程间修改即时可见。
    • 示例:线程 A 修改 volatile 变量后,线程 B 能立即读取最新值,避免缓存不一致问题。
  2. 禁止指令重排序

    • 通过内存屏障防止编译器和处理器对指令进行重排序优化。
    • 应用:单例模式中避免获取未完全初始化的对象。
  3. 适用场景

    • 状态标志:标记线程状态(如任务完成、中断标志),确保状态变更可见。
    • 硬件寄存器访问:嵌入式开发中读取可能被硬件修改的寄存器值,防止读取旧值。
    • 轻量级同步:读多写少且无需原子性保证的场景(如计数器标记)。
  4. 局限性

    • 不保证原子性:单次读/写原子性,复合操作(如 i++)需依赖 synchronized 或原子类。
    • 性能开销:频繁读写时强制刷新主内存会增加性能损耗。
  5. 语言差异

    • Java:通过内存屏障实现可见性和有序性,与 synchronized 和原子类配合使用。
    • C/C++:仅禁止编译器优化,需结合锁或原子操作实现线程安全。
 volatile 可以保证线程安全吗?
  1. volatile 的线程安全保证范围

    • 可见性保证:变量修改后立即刷新到主内存,其他线程读取时强制获取最新值(如状态标志变更避免死循环问题)。
    • 有序性保证:通过内存屏障禁止指令重排序,确保代码执行顺序与编写顺序一致(如单例模式避免获取未初始化对象)。
  2. volatile 的局限性

    • 不保证原子性:复合操作(如 i++)即使修饰为 volatile 仍可能因线程切换导致数据不一致。
    • 适用场景限制:仅适用于读多写少且操作简单的场景(如状态标志、硬件寄存器访问)。
  3. 正确使用 volatile 的场景

    • 状态标志控制:如 volatile boolean flag 控制任务启停,保证状态变更可见性。
    • 双重检查锁定单例模式:结合 volatilesynchronized 避免指令重排序引发的初始化问题。
    • 轻量级同步:读多写少场景下减少锁竞争开销。
  4. 线程安全的完整实现方案

    • 原子类(如 AtomicInteger:通过 CAS + volatile 组合保证原子性和可见性(适用于计数器等场景)。
    • synchronized 或显式锁:对复合操作(如转账、库存扣减)提供原子性保证,但引入锁竞争开销。
juc 包下你常用的类?
  1. 并发集合类

    • ConcurrentHashMap:分段锁实现的线程安全哈希表,适用于高并发读写(如缓存)。
    • CopyOnWriteArrayList:写时复制列表,读多写少场景高效(如监听器列表)。
  2. 同步工具类

    • CountDownLatch:倒计时门闩,主线程等待多个子任务完成(如系统初始化)。
    • CyclicBarrier:可重用屏障,多线程同步至同一阶段(如分批次数据处理)。
    • Semaphore:控制并发资源访问数(如连接池限流)。
  3. 执行框架类

    • ThreadPoolExecutor:灵活配置线程池参数(核心线程数、队列策略等)。
    • CompletableFuture:异步编程工具,支持链式调用和组合任务(如并行计算编排)。
  4. 原子操作类

    • AtomicInteger:基于 CAS 的无锁计数器,高性能并发计数(如请求量统计)。
    • AtomicReference:原子更新对象引用(如状态标志的无锁更新)。
  5. 锁与条件类

    • ReentrantLock:可中断、超时的可重入锁,替代 synchronized
    • StampedLock:乐观读锁,减少读写锁竞争(如读多写少的场景优化)。
    • Condition:细粒度条件等待/唤醒,支持多条件队列(如生产者-消费者模型)。
  6. 并发队列类

    • LinkedBlockingQueue:无界或容量可选阻塞队列(如任务缓冲队列)。
    • SynchronousQueue:直接传递任务的队列(如线程池的立即匹配策略)。
Java 中有哪些常用的锁,在什么场景下使用?
  1. 互斥锁

    • synchronized
      • 特点:JVM 内置锁,自动管理锁的获取与释放,支持可重入。
      • 场景:简单临界区保护(如计数器操作)、单例模式的双重检查锁。
    • ReentrantLock
      • 特点:支持可中断锁、公平锁、条件变量,需手动释放锁。
      • 场景:复杂同步需求(如超时控制、多条件判断)。
  2. 读写锁

    • ReentrantReadWriteLock
      • 特点:读操作共享,写操作独占,提升读多写少场景性能。
      • 场景:缓存系统、配置文件读取、共享文档操作。
  3. 无锁机制

    • 原子类(AtomicInteger 等)
      • 特点:基于 CAS 实现无锁线程安全操作。
      • 场景:高并发计数(如请求量统计、状态标志更新)。
    • volatile
      • 特点:保证变量可见性,需结合锁保证复合操作原子性。
      • 场景:简单状态标志(如线程启停控制)。
  4. 乐观锁与悲观锁

    • 乐观锁
      • 实现:CAS、版本号机制(如数据库版本字段)。
      • 场景:读多写少场景(如商品库存、点赞计数)。
    • 悲观锁
      • 实现:synchronizedReentrantLock、数据库 SELECT FOR UPDATE
      • 场景:写操作频繁场景(如金融交易、库存扣减)。
  5. 公平锁与非公平锁

    • 公平锁
      • 特点:按请求顺序分配锁,避免线程饥饿。
      • 场景:任务调度需严格顺序(如转账操作、数据库连接池)。
    • 非公平锁
      • 特点:允许线程插队,吞吐量更高。
      • 场景:高并发读操作(如缓存读取、秒杀系统)。
  6. 分段锁与锁优化

    • ConcurrentHashMap 的分段锁
      • 特点:锁粒度细化到数据段,减少锁竞争。
      • 场景:高并发哈希表操作。
    • 锁粗化与锁消除
      • 特点:JVM 自动优化减少锁开销。
      • 场景:循环内重复加锁、明显无竞争的同步代码。
怎么在实践中用锁的?
  1. 互斥锁的实践要点

    • synchronized 关键字
      • 临界区保护:通过同步方法或代码块保护共享资源(如计数器操作)。
      • 锁对象选择:指定特定对象作为锁缩小粒度(如 synchronized (lockObject))。
      • 单例模式:双重检查锁定需结合 volatile 避免指令重排序。
    • ReentrantLock 高级控制
      • 可中断锁:lockInterruptibly() 避免永久阻塞(如服务端请求处理)。
      • 超时机制:tryLock(1, TimeUnit.SECONDS) 防止死锁(分布式锁场景)。
      • 条件变量:newCondition() 实现线程协作(生产者-消费者模型)。
  2. 读写分离场景实践

    • ReentrantReadWriteLock
      • 缓存系统:读锁允许多线程并发读,写锁保证数据更新原子性。
      • 配置热更新:读锁读取配置,写锁更新配置实现动态加载。
    • StampedLock 性能优化
      • 乐观读模式:tryOptimisticRead() 提升读多写少场景吞吐量(如金融行情读取)。
      • 三维空间计算:保护多个关联变量原子更新(如坐标 x,y,z)。
  3. 线程安全工具类应用

    • 原子类与无锁编程
      • 计数器场景:AtomicInteger 替代同步锁提升性能 5-10 倍。
      • 状态标志:volatile 修饰简单状态变量(需注意复合操作加锁)。
    • 阻塞队列实践
      • 生产者-消费者模型:BlockingQueue 自动处理阻塞与唤醒(如 LinkedBlockingQueue)。
  4. 锁使用最佳实践

    • 死锁预防
      • 统一加锁顺序:按锁对象 hashCode 排序。
      • 资源限时获取:tryLock 设置超时(推荐 100ms-1s)。
    • 性能优化技巧
      • 锁粒度控制:拆分全局锁为分段锁(如 ConcurrentHashMap 桶锁)。
      • 锁消除与粗化:依赖 JVM 自动优化循环内同步代码。
    • 资源管理规范
      • try-finally 范式:确保任何路径释放锁(如 finally { lock.unlock() })。
      • AutoCloseable 集成:支持 try-with-resources 自动释放锁。
  5. 典型场景锁选型参考

    • 简单临界区:synchronized(单例模式、计数器)。
    • 超时/中断控制:ReentrantLock(分布式任务调度)。
    • 读多写少(>10:1):ReentrantReadWriteLock(文档协作系统)。
    • 极高读取并发:StampedLock(实时行情推送)。
    • 无数据竞争:原子类(请求统计、ID 生成器)。
    • 线程协作:Condition + ReentrantLock(订单状态机)。
除了用 synchronized,还有什么方法可以实现线程同步?
  1. 显式锁(ReentrantLock)

    • 手动控制锁的获取与释放(lock()/unlock()),支持公平锁和非公平锁模式。
    • 特性:
      • 超时获取(tryLock)和可中断锁(lockInterruptibly)。
      • 多条件变量(Condition)实现精准线程唤醒。
    • 场景:高并发资源保护(如转账逻辑)、公平调度系统。
  2. 原子操作类(Atomic 类)

    • 使用 AtomicIntegerAtomicReference 等通过 CAS 保证单变量原子性。
    • 场景:简单计数器、状态标志(如 volatile + AtomicBoolean)。
  3. 信号量(Semaphore)

    • 通过计数器控制资源并发访问数(acquire()/release())。
    • 场景:数据库连接池限制、并发下载流量控制。
  4. 读写锁(ReentrantReadWriteLock)

    • 读锁共享,写锁独占。
    • 场景:读多写少的配置管理、缓存系统热更新。
  5. 线程本地存储(ThreadLocal)

    • 为每个线程创建变量副本,避免共享变量同步问题。
    • 场景:数据库连接管理、线程不安全工具类(如日期格式化)。
  6. 条件变量(Condition)

    • 配合锁实现精准唤醒(await()/signal())。
    • 场景:生产者-消费者模型中的满/空队列分离控制。
  7. volatile 关键字

    • 保证变量可见性和禁止指令重排序。
    • 场景:状态标志(任务终止标记)、双重检查锁定单例模式。
  8. 阻塞队列(BlockingQueue)

    • 内置线程安全机制,自动处理阻塞与唤醒(put()/take())。
    • 实现:ArrayBlockingQueue(任务调度)、LinkedBlockingQueue(日志异步处理)。
synchronized 和 Reentrantlock 区别?
  1. 实现机制

    • synchronized:基于 JVM 的监视器锁(Monitor),通过对象头管理锁状态,自动处理加锁与释放。
    • ReentrantLock:基于 JDK 的 Lock 接口,依赖 AQS 队列和 CAS 操作,需显式调用 lock()/unlock()
  2. 功能特性

    • 锁类型
      • synchronized:仅非公平锁,线程获取顺序随机。
      • ReentrantLock:支持公平锁(按请求顺序)和非公平锁(默认)。
    • 中断与超时
      • ReentrantLock:支持 lockInterruptibly() 响应中断,tryLock(timeout) 超时机制。
      • synchronized:线程阻塞后无法中断,可能导致死锁。
    • 条件变量
      • ReentrantLock:通过 newCondition() 创建多个 Condition 实现精准唤醒(如生产者-消费者模型)。
      • synchronized:仅支持 wait()/notify() 单路通知。
  3. 性能与优化

    • 低竞争场景:synchronized 因偏向锁、轻量级锁优化性能接近 ReentrantLock。
    • 高并发场景:ReentrantLock 非公平锁通过 CAS 减少线程切换,性能更优;synchronized 升级为重量级锁后开销大。
  4. 使用方式

    • synchronized:语法简洁,自动释放锁,适用于方法或代码块同步。
    • ReentrantLock:需显式调用 lock()/unlock(),支持 tryLock() 和超时机制,结合 try-finally 确保释放。
  5. 适用场景

    • synchronized:简单同步需求(实例变量保护)、低竞争场景、快速开发。
    • ReentrantLock:复杂同步逻辑(公平锁、中断响应)、高并发优化、多条件协作(任务调度优先级控制)。
  6. 锁的释放与可重入性

    • synchronized:可重入且自动释放锁,JVM 在同步代码结束时解锁。
    • ReentrantLock:可重入但需手动释放,未正确 unlock() 可能导致死锁。
  7. 注意事项

    • 锁粒度:synchronized 静态方法锁定类对象可能影响性能,ReentrantLock 可通过细粒度条件优化。
    • 死锁预防:ReentrantLock 的 tryLock() 和超时机制减少死锁概率,synchronized 需避免嵌套锁。
    • 锁升级机制:synchronized 从偏向锁逐步升级为重量级锁,ReentrantLock 优化需开发者控制。
CAS 和 AQS 有什么关系?
  1. 底层实现的依赖关系

    • CAS 是 AQS 的原子操作基础
      • AQS 通过 compareAndSetState() 方法实现 state 的原子更新(如获取锁时从 0 改为 1)。
      • CLH 队列的节点插入与删除(如 enq() 方法)依赖 CAS 保证线程安全。
  2. 协同应用的场景

    • 锁的公平性与非公平性实现
      • 非公平锁:直接通过 CAS 抢占 state(如 ReentrantLock 默认模式)。
      • 公平锁:在 tryAcquire() 中先检查队列状态,再通过 CAS 修改 state
    • 资源释放与唤醒机制
      • AQS 的 release() 方法通过 CAS 重置 state,并唤醒队列中的下一个线程。
  3. 功能定位的区别

    • CAS 的轻量级特性
      • 单变量原子操作(如计数器增减),无需锁机制,适用于低竞争场景。
    • AQS 的同步框架特性
      • 管理多线程竞争资源的排队、阻塞与唤醒(如 Semaphore 控制并发数)。
      • 支持复杂同步逻辑(如读写锁分离、条件变量 Condition)。
  4. 性能优化的互补性

    • CAS 的局限性
      • ABA 问题通过 AtomicStampedReference 解决。
      • 高竞争下自旋开销由 AQS 队列阻塞机制缓解。
    • AQS 的扩展性
      • 结合 CAS 与队列机制平衡吞吐量(如非公平锁优先 CAS 抢占)。
      • 支持可重入性(通过 state 计数器记录锁持有次数)。
Threadlocal 作用,原理,具体里面存的 key value 是啥,会有什么问题,如何解决?
  1. 作用

    • 线程数据隔离:为每个线程创建独立变量副本,避免多线程共享数据同步开销。
    • 上下文管理:存储线程级上下文信息(如用户会话、数据库连接),简化参数传递。
  2. 原理

    • ThreadLocalMap 结构:每个线程维护一个 ThreadLocalMap,以 ThreadLocal 对象为键(弱引用),用户数据为值(强引用)。
    • 操作机制:通过 set()/get() 读写当前线程的 ThreadLocalMapremove() 清理数据。
    • 初始化:使用 withInitial() 设置默认值,首次 get() 触发初始化。
  3. 存储的 key-value

    • KeyThreadLocal 实例(弱引用),通过 threadLocalHashCode 唯一标识。
    • Value:线程本地数据(强引用),如计数器、会话对象。
  4. 潜在问题

    • 内存泄漏
      • 原因:Entry 的 key 被 GC 回收后,value 仍占用内存(尤其线程池复用线程时)。
      • 场景:未调用 remove() 导致残留数据长期存在。
    • 数据污染:线程池复用线程时,残留数据被后续任务误用。
    • 性能开销:哈希冲突使用线性探测法,高并发效率降低。
  5. 解决方案

    • 及时清理:在 try-finally 中调用 remove() 确保资源释放。
    • 弱引用优化:用 static final 修饰 ThreadLocal 实例,减少 key 回收风险。
    • 避免大对象:优先存储轻量级数据,大型对象改用外部缓存。
    • 数据隔离检查:线程池任务执行前显式清理 ThreadLocal 数据。
指令重排序的原理是什么?
  1. 编译器优化重排序

    • 消除冗余:删除重复计算或无效指令(如未使用的变量赋值)。
    • 指令调度:将无数据依赖的指令交错排列以填充 CPU 流水线。
    • 循环展开:复制循环体减少分支预测开销。
  2. 处理器动态重排序

    • 乱序执行(OoOE)
      • 使用重排序缓冲区(ROB)存储未提交指令,通过保留站监控操作数可用性。
    • 内存系统优化
      • 写缓冲区(Write Buffer)延迟内存写入,合并多次写操作。
      • 缓存 Bank 划分允许并行访问不同存储单元。
  3. 重排序的约束条件

    • 数据依赖性限制
      • 真数据依赖(RAW)不可重排,反依赖(WAR)和输出依赖(WAW)可通过寄存器重命名解除。
    • 控制依赖性限制
      • 分支预测允许提前执行推测路径指令(预测失败则丢弃结果)。
  4. 重排序的动机与效果

    • 性能提升机制
      • 减少流水线停顿,隐藏内存延迟,提高缓存命中率。
    • 典型优化效果
      • 提升流水线指令吞吐量,通过寄存器重命名实现多指令并行。
  5. 重排序引发的问题与解决方案

    • 多线程可见性问题
      • 写缓冲区延迟导致读取旧值:使用内存屏障指令(如 x86 的 MFENCE)强制刷新。
    • 有序性破坏
      • 对象未初始化被使用:通过 volatile 关键字插入 LoadLoad/StoreStore 屏障。
volatile 和 sychronized 比较?
  1. 可见性保证

    • volatile:通过内存屏障强制将修改值刷新到主内存,禁止指令重排序(LoadLoad/StoreStore 屏障)。
    • synchronized:锁机制强制同步块内变量从主内存读取/刷新。
  2. 原子性差异

    • volatile:仅保证单次读/写原子性(如 boolean 赋值),不支持复合操作(如 i++)。
    • synchronized:通过独占锁保证代码块内所有操作的原子性。
  3. 作用范围与实现机制

    • volatile:仅修饰变量,基于 JVM 内存模型实现无锁同步。
    • synchronized:可修饰方法或代码块,基于对象监视器锁(Monitor)实现互斥访问。
  4. 性能与锁机制

    • volatile:无锁机制,无线程阻塞,适合高频读操作。
    • synchronized:涉及锁获取/释放,可能触发线程阻塞,但 JVM 优化后性能提升(偏向锁、轻量级锁)。
  5. 适用场景

    • volatile:单例模式 DCL、状态标志(如 isRunning)、硬件寄存器访问。
    • synchronized:需要原子性的复合操作(如计数器递增)、临界区资源保护(如文件读写)。
  6. 有序性处理

    • volatile:通过内存屏障禁止指令重排序,保证操作顺序与代码一致。
    • synchronized:隐式通过锁保证有序性,无法主动禁止重排序。
  7. 可重入性与灵活性

    • volatile:不支持重入,仅适用于简单变量同步。
    • synchronized:支持可重入锁(如递归调用)。

线程池

线程池工作队列满了有哪些拒接策略?
  1. AbortPolicy (中止策略)

    • 行为:直接抛出 RejectedExecutionException 异常中断任务提交。
    • 适用场景:对任务完整性要求极高的场景(如金融交易),需配合异常捕获机制。
    • 风险:未捕获异常可能导致程序中断。
  2. CallerRunsPolicy (调用者运行策略)

    • 行为:将任务回退到提交任务的线程(如主线程)直接执行。
    • 适用场景:生产速度远高于消费速度时自然限流,允许短暂阻塞主线程的非实时任务。
    • 注意事项:避免关键线程(如 UI 主线程)被阻塞。
  3. DiscardPolicy (静默丢弃策略)

    • 行为:无提示丢弃新任务,不抛异常也不执行。
    • 适用场景:允许任务丢失的非关键场景(如实时监控采样)。
    • 风险:需监控丢弃任务量,避免数据不一致。
  4. DiscardOldestPolicy (弃老策略)

    • 行为:丢弃队列中最旧任务,重试提交当前新任务。
    • 适用场景:新任务优先级高于旧任务(如实时消息覆盖历史数据)。
    • 风险:可能丢弃重要旧任务,需评估业务容忍度。
  5. 自定义拒绝策略

    • 实现方式:实现 RejectedExecutionHandler 接口扩展逻辑。
    • 典型方案:
      • 持久化存储:任务存入数据库/Redis 后续处理。
      • 异步重试:延迟后重新提交或转移至备用线程池。
      • 告警通知:触发监控并记录任务详情。
    • 适用场景:需保障任务最终执行的业务(如订单支付)。
  6. 策略选择原则

    • 任务重要性:关键任务优先用 CallerRunsPolicy 或自定义策略。
    • 系统容忍度:允许丢失用 DiscardPolicy,需时效性用 DiscardOldestPolicy
    • 资源限制:内存敏感场景避免无界队列,明确队列容量。
    • 监控配套:静默丢弃策略需加强队列长度和拒绝次数监控。
核心线程数设置为 0 可不可以?
  1. 可以设置但需谨慎权衡

    • 技术上允许将核心线程数设为 0,线程池通过非核心线程处理任务。
    • 任务提交逻辑
      • 当前工作线程为 0 时创建非核心线程执行任务。
      • 已有非核心线程且队列未满时,任务入队等待。
    • 适用场景
      • 极低频非关键任务(如日志清理),避免常驻线程占用资源。
      • 突发流量后需快速释放资源的临时任务(如活动促销统计)。
  2. 潜在风险与限制

    • 线程无法复用:每次任务可能触发新建线程,增加创建/销毁开销。
    • 队列容量敏感
      • 无界队列(如 LinkedBlockingQueue)可能导致 OOM。
      • 有界队列需精确设置容量防止任务堆积。
    • 响应延迟:首次任务需等待线程创建,影响实时性要求高的场景。
  3. 替代优化方案

    • 动态核心线程:通过 allowCoreThreadTimeOut(true) 允许核心线程超时回收,保留复用能力。
    • 弹性线程池
      • 根据 QPS 动态调整 corePoolSize(如夜间降为 0,高峰期恢复)。
      • 结合监控告警自动扩容/缩容(如 Prometheus + 动态配置中心)。
  4. 实践建议

    • 稳定性要求高的系统(如支付服务)避免核心线程数为 0,优先动态调整策略。
    • 测试环境需验证线程创建速率和任务响应时间,防止生产环境性能瓶颈。
线程池中 shutdown (),shutdownNow() 这两个方法有什么作用?
  1. shutdown() 的作用

    • 新任务处理:立即拒绝新任务提交,触发拒绝策略(如抛 RejectedExecutionException)。
    • 现有任务处理:等待已提交任务(包括执行中和队列中的)全部完成。
    • 状态变化:将线程池状态从 RUNNING 变为 SHUTDOWN,最终进入 TERMINATED
    • 线程中断:仅中断空闲线程,不干扰执行中的任务。
  2. shutdownNow() 的作用

    • 新任务处理:立即拒绝新任务提交。
    • 现有任务处理
      • 尝试中断所有正在执行的任务(通过 interrupt())。
      • 清空队列并返回未执行任务列表(List<Runnable>)。
    • 状态变化:将状态从 RUNNING 改为 STOP,最终进入 TERMINATED
    • 线程中断:强制中断所有工作线程,无论是否空闲。
  3. 关键差异对比

    • 新任务处理:均拒绝新任务。
    • 现有任务处理
      • shutdown() 等待任务完成。
      • shutdownNow() 中断任务并丢弃未执行任务。
    • 线程中断范围
      • shutdown() 仅中断空闲线程。
      • shutdownNow() 中断所有线程。
    • 返回值
      • shutdown() 无返回值。
      • shutdownNow() 返回未执行任务列表。
    • 适用场景
      • shutdown():优雅停机(如服务正常退出)。
      • shutdownNow():紧急终止(如死锁或资源耗尽)。
  4. 使用建议

    • 组合调用:先调用 shutdown() 等待任务完成,若超时未终止再调用 shutdownNow(),需捕获 InterruptedException 并处理。
    • 任务容错设计:在任务代码中检查中断状态,确保能响应中断请求。
  5. 注意事项

    • 资源泄漏:未正确关闭线程池可能导致线程和资源(如数据库连接)无法释放。
    • 队列影响
      • 无界队列使用 shutdown() 可能导致 OOM。
      • shutdownNow() 清空队列可缓解内存压力。
提交给线程池中的任务可以被撤回吗?
  1. 通过 Future 对象取消任务

    • 提交任务后返回 Future 对象,调用 cancel(boolean mayInterruptIfRunning) 可尝试取消任务。
    • 参数作用
      • mayInterruptIfRunning = true:若任务已执行,触发线程中断(需任务代码响应中断)。
      • mayInterruptIfRunning = false:仅取消未开始执行的任务。
    • 返回值
      • true:任务成功取消(未开始或已响应中断)。
      • false:任务已完成或无法取消。
  2. 不同任务状态下的撤回结果

    • 任务未开始执行cancel() 可成功移除队列中的任务。
    • 任务正在执行:依赖任务代码的中断响应逻辑(如检查 isInterrupted() 或捕获 InterruptedException)。
    • 任务已完成:无法撤回,cancel() 返回 false
  3. 线程池实现的影响

    • ThreadPoolExecutor 的局限性
      • 无界队列(如 LinkedBlockingQueue)中的任务无法直接撤回,需通过 Future.cancel()
      • remove(Runnable task) 方法可移除队列中未执行的任务,但需明确任务引用。
    • 定时任务(ScheduledThreadPool
      • ScheduledFuture.cancel() 可终止周期性任务,已开始的单次任务仍需中断响应逻辑。
  4. 实践建议

    • 代码容错设计:任务逻辑中定期检查中断状态,确保能响应取消请求。
    • 组合关闭策略
      • 先调用 shutdown() 等待任务完成,超时后使用 shutdownNow() 强制终止。
    • 避免无界队列:使用有界队列(如 ArrayBlockingQueue)减少无法撤回任务的风险。

场景

多线程打印奇偶数,怎么控制打印的顺序
  1. 基础同步锁方案

    • 同步机制:使用 synchronized 保证原子操作,避免竞态条件。
    • 双重检查机制
      • 外层 while(num <= 100) 控制整体循环。
      • 内层 while(num%2 ==0) 实现条件等待。
    • 线程协作
      • wait() 释放锁并暂停当前线程。
      • notify() 唤醒等待线程。
  2. 标志位优化方案

    • 显式状态控制:通过 isOddTurn 布尔标志管理奇偶切换。
    • 性能优化
      • 避免频繁计算奇偶性,提升执行效率。
      • 便于扩展多线程交替逻辑(如三线程 ABC 交替)。
  3. 高级锁机制方案

    • 精细控制:使用 ReentrantLock 替代 synchronized,提供更灵活的锁管理。
    • 条件队列
      • 通过 Condition 对象(oddCondevenCond)分别管理奇偶线程等待队列。
      • signal() 精准唤醒目标线程。
  4. 实现要点总结

    • 同步机制选择:简单场景用 synchronized,复杂场景用 ReentrantLock
    • 条件判断:必须使用 while 循环检查条件,避免虚假唤醒。
    • 数值递增:在同步代码块内完成修改,保证原子性。
    • 终止条件:循环外设置退出检测(如 num > 100),防止数值越界。
    • 异常处理:捕获 InterruptedException 并确保锁最终释放。
  5. 调试与扩展

    • 调试建议
      • 通过 Thread.setName() 设置线程名称,便于日志追踪。
      • 使用 jstack 检测死锁,VisualVM 监控线程状态。
    • 扩展应用场景
      • 多阶段交替:修改为三个线程交替打印(如 ABCABC 序列)。
      • 动态调整上限:通过 volatile 变量实现运行时修改最大值。
      • 分布式扩展:结合消息队列(如 Kafka)实现跨进程顺序控制。
单例模型既然已经用了 synchronized,为什么还要在加 volatile?
  1. 解决指令重排序问题

    • 对象实例化操作 instance = new Singleton() 在 JVM 中分为三步:
      • 分配内存空间
      • 调用构造函数初始化对象
      • 将对象引用赋值给变量
    • volatile 时可能发生指令重排序(如顺序变为 ①→③→②),导致其他线程获取未初始化完成的实例。
    • volatile 通过内存屏障禁止指令重排序,确保初始化顺序为 ①→②→③,避免「半初始化」对象。
  2. 保证可见性

    • 当首个线程完成实例化后,volatile 强制将最新值刷新到主内存,其他线程立即感知 instance 非空,避免重复创建。
    • 仅用 synchronized 时,其他线程可能因本地缓存未更新而误判 instance 为空。
  3. 双重检查锁定的完整性

    • 第一次判空:减少锁竞争,仅实例未初始化时进入同步块。
    • 第二次判空:防止多个线程通过第一次检查后重复初始化。
    • volatile 配合:确保第二次判空时读取最新值,避免指令重排序或可见性问题导致判空失效。
  4. 性能与安全的平衡

    • synchronized 保证原子性,但无法单独解决指令重排序和可见性问题。
    • volatile 以较低性能代价(内存屏障)补充 synchronized 的不足,使 DCL 单例高效且线程安全。
3 个线程并发执行,1 个线程等待这三个线程全部执行完在执行,怎么实现?
  1. 使用 CountDownLatch

    • 核心机制:创建初始值为 3 的计数器,三个线程执行完毕后调用 countDown() 减少计数。
    • 等待线程:通过 await() 阻塞至计数器归零后执行后续任务。
  2. 线程 Join 方法

    • 实现方式:在等待线程中依次调用三个并发线程的 join(),使其阻塞直至所有目标线程结束。
    • 注意事项
      • 需按顺序先启动并发线程再启动等待线程。
      • 适用于简单场景,代码耦合度较高。
  3. CompletableFuture 组合

    • 核心方法:通过 CompletableFuture.allOf() 等待所有异步任务完成。
    • 优势:支持异步任务组合,可链式调用后续操作(如 thenRun())。
  4. 线程池与 awaitTermination

    • 实现流程
      • 将三个任务提交至线程池。
      • 调用 shutdown() 关闭线程池后,使用 awaitTermination() 等待任务完成。
    • 优势:适合批量任务管理,可设置超时时间防止无限等待。
  5. 信号量 (Semaphore)

    • 控制逻辑
      • 初始化三个许可,等待线程需调用 acquire(3) 获取全部许可后执行。
      • 每个并发线程执行后调用 release() 释放许可。
    • 适用场景:支持动态调整等待线程数量,灵活性较高。
  6. 方案选择建议

    • 轻量级场景:优先选择 CountDownLatchCompletableFuture,代码简洁且控制精准。
    • 批量任务管理:推荐线程池的 awaitTermination,便于扩展和资源管理。
    • 动态调整需求:使用 Semaphore 可灵活控制许可数量。
    • 异常处理:所有方案需注意线程泄漏和死锁风险,确保资源释放。
假设两个线程并发读写同一个整型变量,初始值为零,每个线程加 50 次,结果可能是什么?
  1. 可能的取值范围

    • 最低结果:0(极端情况下所有加操作被覆盖)。
    • 最高结果:100(理想线程串行执行)。
    • 实际常见值:50~100 之间波动,可能更小(如交替执行多次覆盖修改)。
  2. 原因分析

    • 非原子操作n += 1 包含读取、修改、写入三步,可能被线程切换打断。
      • 示例:线程 A 读 n=0 → 线程 B 读 n=0 → 均写入 n=1,导致两次操作仅生效一次。
    • 缓存可见性问题:多核 CPU 缓存未同步,线程可能读取旧值(如线程 A 修改后未刷回主存,线程 B 仍用 n=0 计算)。
    • 指令重排序:编译器或 CPU 调整指令顺序,延迟写入操作加剧数据不一致。
  3. 解决方案

    • 同步锁
      • 使用 synchronizedReentrantLock 保证临界区代码原子性。
    • 原子变量
      • AtomicInteger 替代普通整型变量,通过 incrementAndGet() 的 CAS 机制保证原子性,性能优于锁。
    • 线程安全容器
      • ConcurrentHashMap 提供细粒度并发控制,但本场景更适用原子变量。

高优先级

线程池种类有哪些?
  1. FixedThreadPool (固定大小线程池)

    • 核心参数corePoolSize = maximumPoolSize,无界队列 LinkedBlockingQueue
    • 特点:线程数固定,适用于任务量稳定场景(如 Web 服务器请求处理)。
    • 风险:无界队列可能导致 OOM。
  2. CachedThreadPool (可缓存线程池)

    • 核心参数:核心线程数 0,最大线程数 Integer.MAX_VALUE,同步队列 SynchronousQueue
    • 特点:动态扩容线程,空闲线程 60 秒回收(适合短期异步任务)。
    • 风险:线程过多可能耗尽资源。
  3. SingleThreadExecutor (单线程池)

    • 核心参数:核心线程数 1,无界队列 LinkedBlockingQueue
    • 特点:保证任务顺序执行(如日志处理、数据库操作)。
  4. ScheduledThreadPool (定时任务线程池)

    • 核心参数:固定核心线程数,延迟队列 DelayedWorkQueue
    • 特点:支持延时/周期性任务(如定时备份、心跳检测)。
  5. WorkStealingPool (工作窃取线程池)

    • 核心参数:基于 ForkJoinPool,默认线程数等于 CPU 核数。
    • 特点:任务窃取机制,适合并行计算(如分治算法、图像处理)。
  6. 自定义线程池

    • 核心参数:通过 ThreadPoolExecutor 定制核心线程数、队列类型及拒绝策略。
    • 适用场景:高并发系统灵活平衡资源与效率。
  7. 其他补充

    • ForkJoinPool:专为分治任务设计(如递归计算、大规模数据处理)。
    • 拒绝策略:支持 AbortPolicy(抛异常)或 DiscardOldestPolicy(丢弃旧任务)等。
线程池的参数有哪些?
  1. corePoolSize (核心线程数)

    • 线程池保持活跃的最小线程数,空闲时不会被销毁(除非设置 allowCoreThreadTimeOut=true)。
    • 新任务提交时优先创建核心线程处理,直到达到阈值。
  2. maximumPoolSize (最大线程数)

    • 允许创建的最大线程总数(核心线程 + 非核心线程)。
    • 当队列已满且线程数小于该值时,创建非核心线程。
  3. keepAliveTime (空闲线程存活时间)

    • 非核心线程空闲时的最大存活时间,超时后销毁。
    • unit 配合使用,默认不影响核心线程。
  4. unit (时间单位)

    • 定义 keepAliveTime 的时间单位(如秒、毫秒)。
  5. workQueue (任务队列)

    • 存储待执行任务的阻塞队列,影响任务调度逻辑。
    • 常见类型
      • LinkedBlockingQueue(无界队列,可能 OOM)。
      • ArrayBlockingQueue(有界队列,推荐使用)。
      • SynchronousQueue(直接传递任务)。
  6. threadFactory (线程工厂)

    • 自定义线程创建方式(命名规则、优先级、守护线程属性)。
    • 默认使用 Executors.defaultThreadFactory()
  7. handler (拒绝策略)

    • 线程和队列均满时处理新任务的策略。
    • 常用策略
      • AbortPolicy(默认,抛异常)。
      • CallerRunsPolicy(提交线程直接执行)。
      • DiscardPolicy(静默丢弃)。
      • DiscardOldestPolicy(丢弃最旧任务)。
  8. 注意事项

    • 队列选择原则
      • CPU 密集型任务推荐有界队列(如 ArrayBlockingQueue)。
      • I/O 密集型任务可考虑无界队列(需防范 OOM)。
    • 参数联动规则
      • 仅当 workQueue 已满且线程数 < maximumPoolSize 时创建非核心线程。
      • 使用无界队列时 maximumPoolSize 无效。
    • 推荐实践:手动创建 ThreadPoolExecutor 控制队列边界,避免使用 Executors 默认实现。
线程池设计
  1. 任务类型决定核心线程数

    • CPU 密集型任务(如加密计算):corePoolSize = CPU 核数 + 1(如 8 核设为 9 线程)。
    • I/O 密集型任务(如网络请求):corePoolSize ≈ 2 * CPU 核数(如 8 核设为 16)。
    • 混合型任务corePoolSize = Ncpu * Ucpu * (1 + W/C),其中 W 为任务等待时间,C 为计算时间。
  2. 队列选择与容量设计

    • 无界队列LinkedBlockingQueue):适用短耗时任务,明确设置容量防止 OOM(如 LinkedBlockingQueue(2000))。
    • 有界队列ArrayBlockingQueue):容量计算为 (corePoolSize / 任务平均耗时) * 最大容忍响应时间
    • 同步队列SynchronousQueue):需高实时性,搭配 maximumPoolSize 设为较大值(如 50-100)。
  3. 线程池扩容与收缩规则

    • 最大线程数:通常设为核心数的 1.5-3 倍(如电商场景核心 85,最大 150)。
    • 空闲线程回收keepAliveTime 设为 30-60 秒,突发流量可延长至 2-5 分钟。
    • 动态调整:根据 QPS 监控自动扩容(新核心数 = 当前 QPS * 平均耗时 / 1000 * 0.8)。
  4. 拒绝策略与容错机制

    • CallerRunsPolicy:允许主线程降级处理(如日志记录)。
    • DiscardOldestPolicy:新任务优先级高(如实时消息覆盖)。
    • 自定义策略
      • 持久化存储:任务存数据库/Redis 后续处理。
      • 异步重试:延迟后提交或转移至备用线程池。
  5. 监控与调优实践

    • 监控指标:活跃线程数、队列堆积量、任务平均耗时(推荐 Prometheus 实时监控)。
    • 压测验证:某电商优化后 QPS 从 4200 提升至 8500,错误率从 15% 降至 0.2%。
    • 弹性设计:容器水平扩容(如 K8s)与线程池参数调优结合应对突发流量。
  6. 避坑指南

    • 避免 corePoolSize = maximumPoolSize(丧失突发能力)。
    • 明确队列容量(禁用默认无界队列)。
    • CPU 密集型任务勿用 CallerRunsPolicy(可能阻塞主线程)。
介绍一下线程池的工作原理
  1. 核心组成与初始化

    • 线程池管理器:负责创建、销毁线程及任务调度,维护线程池生命周期。
    • 任务队列:存储待执行任务,常用阻塞队列(如 LinkedBlockingQueue),队列满时触发拒绝策略。
    • 工作线程:核心线程常驻处理任务,非核心线程队列满时动态创建,空闲超时后销毁。
    • 线程工厂与拒绝策略:自定义线程属性(如命名规则),定义队列满时的处理逻辑(如抛异常)。
  2. 任务处理流程

    • 任务提交:通过 submit()execute() 提交 Runnable/Callable 任务。
    • 资源分配
      • 线程数 < 核心线程数:直接创建新线程执行。
      • 核心线程已满:任务入队等待。
      • 队列满且线程数 < 最大线程数:创建救急线程处理。
      • 队列和线程均满:触发拒绝策略(如 AbortPolicy)。
    • 任务执行:工作线程从队列取任务执行,完成后等待新任务。
  3. 线程池状态管理

    • 状态标识:通过 ctl 变量(高3位状态,低29位线程数)维护:
      • RUNNING:正常接收并执行任务。
      • SHUTDOWN:不再接收新任务,处理存量任务。
      • STOP:中断所有任务,丢弃未处理任务。
      • TERMINATED:线程池完全终止。
    • 状态转换shutdown() 进入 SHUTDOWN,shutdownNow() 进入 STOP,完成后转为 TERMINATED。
  4. 性能优化机制

    • 线程复用:避免频繁创建/销毁,减少上下文切换(每次 1-10 微秒)。
    • 动态伸缩:根据任务量调整救急线程数量,空闲超时后回收。
    • 队列选择
      • CPU 密集型:有界队列(如 ArrayBlockingQueue)。
      • I/O 密集型:无界队列(如 LinkedBlockingQueue)。
    • 拒绝策略适配:电商系统采用 CallerRunsPolicy 由提交线程执行任务。
  5. 典型应用场景

    • Web 服务器:Tomcat 用固定核心线程数(如200)处理 HTTP 请求。
    • 异步任务处理:日志写入用缓存线程池(CachedThreadPool)动态扩容。
    • 定时任务调度ScheduledThreadPool 支持延迟/周期性任务(如数据同步)。
    • 数据库连接池:复用连接减少建立开销(类比线程池设计)。
为啥 Java 线程池核心线程满后,入队列等待,Tomcat 核心满后不入队列等待的原因
  1. 设计目标差异

    • Java 线程池:面向 CPU 密集型任务,优先用队列缓冲任务减少线程创建开销。核心线程满后任务入队,队列满时才创建非核心线程。
    • Tomcat 线程池:针对 I/O 密集型场景优化,优先创建新线程降低延迟,核心满后直接扩容线程至最大值,队列仅作最后缓冲。
  2. 队列行为实现机制

    • Java 默认队列:使用 LinkedBlockingQueue,核心线程满后直接入队,无界队列可能导致 OOM。
    • Tomcat 自定义队列
      • 重写 offer() 方法:当已提交任务数超过活跃线程数时返回 false 触发创建新线程。
      • 仅在线程达最大值或队列满时才真正入队。
  3. 线程创建策略对比

    • Java 线程池:严格遵循「核心线程→队列→非核心线程」流程,队列未满不扩容。
    • Tomcat 线程池:核心线程满后,若提交任务数大于当前线程数,立即创建新线程直至最大值。
  4. 性能与资源权衡

    • Java 队列优先:适合耗时长的 CPU 运算任务,减少线程切换损耗。
    • Tomcat 线程优先:避免队列延迟,快速响应突发流量(如秒杀请求),默认限制队列长度(acceptCount=100)防 OOM。
  5. 参数配置体现差异

    • Java 参数corePoolSizemaximumPoolSize 分离,依赖队列容量控制资源。
    • Tomcat 参数
      • maxThreads 限制线程总数,acceptCount 限制队列容量。
      • minSpareThreads 预创建核心线程,减少首次请求延迟。
为什么不推荐用内置线程池
  1. 资源耗尽风险

    • 无界队列导致 OOM
      • FixedThreadPoolSingleThreadExecutor 使用 LinkedBlockingQueue,任务提交速度过快时队列无限堆积可能引发内存溢出。
      • ScheduledThreadPool 的延迟队列容量为 Integer.MAX_VALUE,周期性任务堆积存在 OOM 隐患。
    • 无界线程池问题
      • CachedThreadPool 允许创建 Integer.MAX_VALUE 线程,高并发下可能耗尽 CPU/内存资源。
  2. 配置灵活性与容错能力不足

    • 参数固定化
      • 内置线程池的队列类型、核心线程数等参数无法动态调整,难以适配突发流量或资源敏感型任务。
    • 默认拒绝策略单一
      • 默认使用 AbortPolicy(抛异常),缺乏降级、重试等容错机制,可能引发服务雪崩。
  3. 可维护性与监控缺陷

    • 线程命名模糊
      • 内置线程池生成的线程名称(如 pool-1-thread-n)无业务含义,增加排查难度。
    • 缺乏监控支持
      • 无法直接获取活跃线程数、队列堆积量等关键指标,需额外开发监控逻辑。
  4. 生产环境最佳实践

    • 推荐替代方案
      • 使用 ThreadPoolExecutor 手动配置:
        • 有界队列:如 ArrayBlockingQueue 控制内存风险。
        • 合理线程数:根据 CPU 核数和任务类型(CPU/I/O 密集型)设置 corePoolSizemaximumPoolSize
        • 自定义拒绝策略:结合 CallerRunsPolicy 或降级逻辑(如任务存入外部队列)。
    • 线程工厂与命名
      • 自定义 ThreadFactory 为线程添加业务标识,便于日志追踪。
    • 生命周期管理
      • 通过 shutdown()shutdownNow() 组合实现优雅关闭,避免线程泄漏。
Last Updated 5/18/2025, 9:21:15 PM