跳到主要内容

Java 并发面试题集

总题数: 62道 | 重点领域: 线程、锁、并发容器、JMM | 难度分布: 中级到高级

本文档整理了 Java 并发的完整62道面试题目,涵盖线程基础、锁机制、并发容器、内存模型等各个方面。


面试题目列表

1. 什么是 Java 中的线程同步?

答案:

线程同步是指协调多个线程对共享资源的访问,确保在同一时刻只有一个线程能够访问特定的资源,从而避免数据不一致和竞态条件。

主要同步机制:

  • synchronized 关键字:提供互斥锁机制
  • Lock 接口:提供更灵活的锁操作(ReentrantLock、ReadWriteLock)
  • volatile 关键字:保证变量的可见性
  • wait/notify 机制:线程间的协作通信
  • 并发工具类:Semaphore、CountDownLatch、CyclicBarrier 等
java
1// synchronized 同步示例
2public class Counter {
3 private int count = 0;
4
5 public synchronized void increment() {
6 count++;
7 }
8
9 public synchronized int getCount() {
10 return count;
11 }
12}

2. Java 中的线程安全是什么意思?

答案:

线程安全是指当多个线程同时访问某个类、对象或方法时,这个类、对象或方法始终能表现出正确的行为,不会出现数据不一致或不可预期的结果。

线程安全的实现方式:

  1. 互斥同步(悲观锁):synchronized、ReentrantLock
  2. 非阻塞同步(乐观锁):CAS 操作、原子类
  3. 无同步方案
    • 栈封闭(局部变量)
    • ThreadLocal
    • 不可变对象(final、String)

线程安全级别:

  • 不可变:String、Integer 等
  • 绝对线程安全:所有操作都是线程安全的
  • 相对线程安全:单次操作是线程安全的,如 Vector、HashTable
  • 线程兼容:需要外部同步,如 ArrayList、HashMap
  • 线程对立:无法在多线程环境使用
java
1// 线程安全的单例模式
2public class Singleton {
3 private static volatile Singleton instance;
4
5 private Singleton() {}
6
7 public static Singleton getInstance() {
8 if (instance == null) {
9 synchronized (Singleton.class) {
10 if (instance == null) {
11 instance = new Singleton();
12 }
13 }
14 }
15 return instance;
16 }
17}

3. 什么是协程?Java 支持协程吗?

答案:

协程(Coroutine) 是一种轻量级的用户态线程,由程序自己控制调度,不需要操作系统内核参与。协程的切换开销远小于线程切换。

协程的特点:

  • 轻量级:占用内存小,创建销毁快
  • 用户态调度:不需要内核态切换
  • 协作式调度:由程序控制何时挂起和恢复
  • 高并发:可以创建大量协程

Java 对协程的支持:

传统 Java(Java 8-18)不直接支持协程,但可以通过以下方式实现类似功能:

  • Quasar 库:提供 Fiber(纤程)实现
  • Kotlin 协程:在 JVM 上运行的协程
  • Project Loom:Java 19+ 引入的虚拟线程(Virtual Threads)
java
1// Java 19+ 虚拟线程示例
2try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
3 IntStream.range(0, 10_000).forEach(i -> {
4 executor.submit(() -> {
5 Thread.sleep(Duration.ofSeconds(1));
6 return i;
7 });
8 });
9}

4. 线程的生命周期在 Java 中是如何定义的?

答案:

Java 线程有 6 种状态,定义在 Thread.State 枚举中:

1. NEW(新建)

  • 线程被创建但还未调用 start() 方法

2. RUNNABLE(可运行)

  • 调用 start() 后,线程可能正在运行或等待 CPU 时间片
  • 包含操作系统的 Ready 和 Running 状态

3. BLOCKED(阻塞)

  • 线程等待获取监视器锁(synchronized)

4. WAITING(等待)

  • 无限期等待另一个线程执行特定操作
  • 触发条件:Object.wait()、Thread.join()、LockSupport.park()

5. TIMED_WAITING(计时等待)

  • 等待指定时间后自动返回
  • 触发条件:Thread.sleep()、Object.wait(timeout)、Thread.join(timeout)

6. TERMINATED(终止)

  • 线程执行完成或异常退出
java
1// 状态转换示例
2public class ThreadStateDemo {
3 public static void main(String[] args) throws InterruptedException {
4 Thread thread = new Thread(() -> {
5 try {
6 Thread.sleep(1000); // TIMED_WAITING
7 synchronized (ThreadStateDemo.class) {
8 ThreadStateDemo.class.wait(); // WAITING
9 }
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13 });
14
15 System.out.println("NEW: " + thread.getState()); // NEW
16 thread.start();
17 Thread.sleep(100);
18 System.out.println("RUNNABLE: " + thread.getState()); // RUNNABLE
19 Thread.sleep(1000);
20 System.out.println("TIMED_WAITING: " + thread.getState()); // TIMED_WAITING
21 }
22}

5. Java 中线程之间如何进行通信?

答案:

Java 提供了多种线程间通信机制:

1. wait/notify/notifyAll 机制

java
1public class WaitNotifyDemo {
2 private static final Object lock = new Object();
3
4 public void producer() throws InterruptedException {
5 synchronized (lock) {
6 System.out.println("生产数据");
7 lock.notify(); // 唤醒等待线程
8 }
9 }
10
11 public void consumer() throws InterruptedException {
12 synchronized (lock) {
13 lock.wait(); // 等待通知
14 System.out.println("消费数据");
15 }
16 }
17}

2. volatile 共享变量

java
1private volatile boolean flag = false;
2
3// 线程1
4flag = true;
5
6// 线程2
7while (!flag) {
8 // 等待
9}

3. CountDownLatch

java
1CountDownLatch latch = new CountDownLatch(3);
2// 线程完成后调用
3latch.countDown();
4// 主线程等待
5latch.await();

4. CyclicBarrier

java
1CyclicBarrier barrier = new CyclicBarrier(3, () -> {
2 System.out.println("所有线程到达屏障");
3});
4barrier.await();

5. Semaphore 信号量

java
1Semaphore semaphore = new Semaphore(3);
2semaphore.acquire(); // 获取许可
3semaphore.release(); // 释放许可

6. BlockingQueue 阻塞队列

java
1BlockingQueue<String> queue = new LinkedBlockingQueue<>();
2queue.put("data"); // 生产者
3String data = queue.take(); // 消费者

7. Condition 条件变量

java
1Lock lock = new ReentrantLock();
2Condition condition = lock.newCondition();
3condition.await(); // 等待
4condition.signal(); // 唤醒

6. Java 中如何创建多线程?

答案:

Java 创建线程有 4 种主要方式:

1. 继承 Thread 类

java
1public class MyThread extends Thread {
2 @Override
3 public void run() {
4 System.out.println("Thread running");
5 }
6}
7
8// 使用
9MyThread thread = new MyThread();
10thread.start();

2. 实现 Runnable 接口

java
1public class MyRunnable implements Runnable {
2 @Override
3 public void run() {
4 System.out.println("Runnable running");
5 }
6}
7
8// 使用
9Thread thread = new Thread(new MyRunnable());
10thread.start();
11
12// Lambda 方式
13new Thread(() -> System.out.println("Lambda")).start();

3. 实现 Callable 接口(有返回值)

java
1public class MyCallable implements Callable<String> {
2 @Override
3 public String call() throws Exception {
4 return "Callable result";
5 }
6}
7
8// 使用
9FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
10new Thread(futureTask).start();
11String result = futureTask.get(); // 获取返回值

4. 使用线程池

java
1ExecutorService executor = Executors.newFixedThreadPool(5);
2
3// 提交 Runnable
4executor.submit(() -> System.out.println("Task"));
5
6// 提交 Callable
7Future<String> future = executor.submit(() -> "Result");
8String result = future.get();
9
10executor.shutdown();

推荐方式:

  • 实现 Runnable/Callable 接口(避免单继承限制)
  • 使用线程池(更好的资源管理和性能)

7. 你了解 Java 线程池的原理吗?

答案:

线程池核心参数(ThreadPoolExecutor):

java
1public ThreadPoolExecutor(
2 int corePoolSize, // 核心线程数
3 int maximumPoolSize, // 最大线程数
4 long keepAliveTime, // 空闲线程存活时间
5 TimeUnit unit, // 时间单位
6 BlockingQueue<Runnable> workQueue, // 任务队列
7 ThreadFactory threadFactory, // 线程工厂
8 RejectedExecutionHandler handler // 拒绝策略
9)

执行流程:

  1. 提交任务时,如果线程数 < corePoolSize,创建新线程执行
  2. 如果线程数 >= corePoolSize,任务放入队列
  3. 如果队列满了且线程数 < maximumPoolSize,创建新线程
  4. 如果线程数 >= maximumPoolSize 且队列满了,执行拒绝策略
1提交任务
2
3线程数 < corePoolSize? → 是 → 创建核心线程执行
4 ↓ 否
5队列未满? → 是 → 加入队列等待
6 ↓ 否
7线程数 < maximumPoolSize? → 是 → 创建非核心线程执行
8 ↓ 否
9执行拒绝策略

示例:

java
1ThreadPoolExecutor executor = new ThreadPoolExecutor(
2 5, // 核心线程数
3 10, // 最大线程数
4 60L, // 空闲线程存活时间
5 TimeUnit.SECONDS,
6 new LinkedBlockingQueue<>(100), // 队列容量
7 Executors.defaultThreadFactory(),
8 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
9);

线程池状态:

  • RUNNING:接受新任务并处理队列任务
  • SHUTDOWN:不接受新任务,但处理队列任务
  • STOP:不接受新任务,不处理队列任务,中断正在执行的任务
  • TIDYING:所有任务已终止
  • TERMINATED:terminated() 方法执行完成

8. 如何合理地设置 Java 线程池的线程数?

答案:

线程数设置需要根据任务类型和系统资源来决定:

1. CPU 密集型任务

  • 特点:大量计算,很少 I/O 操作
  • 推荐线程数:CPU 核心数 + 1
  • 原因:避免过多线程切换开销
java
1int cpuCount = Runtime.getRuntime().availableProcessors();
2int threadCount = cpuCount + 1;

2. I/O 密集型任务

  • 特点:大量网络、磁盘 I/O 操作
  • 推荐线程数:CPU 核心数 × 2CPU 核心数 / (1 - 阻塞系数)
  • 阻塞系数:0.8-0.9 之间
java
1int cpuCount = Runtime.getRuntime().availableProcessors();
2// 方式1
3int threadCount = cpuCount * 2;
4
5// 方式2(更精确)
6double blockingCoefficient = 0.9; // I/O 时间占比
7int threadCount = (int) (cpuCount / (1 - blockingCoefficient));

3. 混合型任务

  • 可以拆分为 CPU 密集和 I/O 密集,分别使用不同线程池

4. 实际考虑因素:

  • 系统内存大小(每个线程占用内存)
  • 任务执行时间
  • 任务优先级
  • 系统负载情况

动态调整示例:

java
1ThreadPoolExecutor executor = new ThreadPoolExecutor(
2 10, 50, 60L, TimeUnit.SECONDS,
3 new LinkedBlockingQueue<>(1000)
4);
5
6// 运行时动态调整
7executor.setCorePoolSize(20);
8executor.setMaximumPoolSize(100);

最佳实践:

  • 通过压测确定最优值
  • 监控线程池运行状态(活跃线程数、队列大小)
  • 根据实际情况动态调整

9. Java 线程池有哪些拒绝策略?

答案:

Java 提供了 4 种内置拒绝策略(RejectedExecutionHandler):

1. AbortPolicy(默认)

  • 直接抛出 RejectedExecutionException 异常
  • 适用于关键任务,不允许丢失
java
1new ThreadPoolExecutor.AbortPolicy()
2// 抛出异常,调用者需要处理

2. CallerRunsPolicy

  • 由调用线程(提交任务的线程)执行该任务
  • 适用于不允许任务丢失,且可以降低提交速度的场景
java
1new ThreadPoolExecutor.CallerRunsPolicy()
2// 主线程执行任务,降低提交速度

3. DiscardPolicy

  • 静默丢弃任务,不抛出异常
  • 适用于允许任务丢失的场景
java
1new ThreadPoolExecutor.DiscardPolicy()
2// 直接丢弃,不做任何处理

4. DiscardOldestPolicy

  • 丢弃队列中最老的任务,然后重新提交当前任务
  • 适用于任务有时效性的场景
java
1new ThreadPoolExecutor.DiscardOldestPolicy()
2// 丢弃队列头部任务,重试当前任务

自定义拒绝策略:

java
1public class CustomRejectedHandler implements RejectedExecutionHandler {
2 @Override
3 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
4 // 记录日志
5 log.warn("Task rejected: {}", r.toString());
6
7 // 保存到数据库或消息队列
8 saveToDatabase(r);
9
10 // 或者使用备用线程池
11 backupExecutor.execute(r);
12 }
13}

选择建议:

  • 关键业务:AbortPolicy + 异常处理
  • 可降级业务:CallerRunsPolicy
  • 允许丢失:DiscardPolicy
  • 时效性任务:DiscardOldestPolicy

10. Java 并发库中提供了哪些线程池实现?它们有什么区别?

答案:

Executors 工厂类提供了几种常用线程池:

1. FixedThreadPool(固定大小线程池)

java
1ExecutorService executor = Executors.newFixedThreadPool(5);
2// 等价于
3new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
4 new LinkedBlockingQueue<Runnable>());
  • 核心线程数 = 最大线程数
  • 使用无界队列 LinkedBlockingQueue
  • 适用于负载较重的服务器,限制线程数

2. CachedThreadPool(缓存线程池)

java
1ExecutorService executor = Executors.newCachedThreadPool();
2// 等价于
3new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
4 new SynchronousQueue<Runnable>());
  • 核心线程数为 0,最大线程数无限
  • 使用 SynchronousQueue(不存储元素)
  • 线程空闲 60 秒后回收
  • 适用于执行大量短期异步任务

3. SingleThreadExecutor(单线程线程池)

java
1ExecutorService executor = Executors.newSingleThreadExecutor();
2// 等价于
3new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
4 new LinkedBlockingQueue<Runnable>());
  • 只有一个线程
  • 保证任务按顺序执行
  • 适用于需要顺序执行的场景

4. ScheduledThreadPool(定时任务线程池)

java
1ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
  • 支持定时和周期性任务执行
  • 使用 DelayedWorkQueue
java
1// 延迟执行
2executor.schedule(task, 5, TimeUnit.SECONDS);
3
4// 固定频率执行
5executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
6
7// 固定延迟执行
8executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);

5. WorkStealingPool(工作窃取线程池,Java 8+)

java
1ExecutorService executor = Executors.newWorkStealingPool();
  • 基于 ForkJoinPool 实现
  • 每个线程有自己的任务队列
  • 空闲线程可以"窃取"其他线程的任务
  • 适用于任务执行时间不均匀的场景

对比总结:

类型核心线程数最大线程数队列适用场景
FixedThreadPoolnn无界队列负载较重的服务器
CachedThreadPool0无限SynchronousQueue大量短期任务
SingleThreadExecutor11无界队列顺序执行任务
ScheduledThreadPoolnInteger.MAX_VALUEDelayedWorkQueue定时任务
WorkStealingPoolCPU核心数-多队列任务时间不均

注意: 阿里巴巴开发手册不推荐使用 Executors 创建线程池,建议手动创建 ThreadPoolExecutor,明确参数,避免资源耗尽风险。

11. Java 线程池核心线程数在运行过程中能修改吗?如何修改?

答案:

可以修改。ThreadPoolExecutor 提供了动态调整线程池参数的方法:

修改核心线程数:

java
1ThreadPoolExecutor executor = new ThreadPoolExecutor(
2 5, 10, 60L, TimeUnit.SECONDS,
3 new LinkedBlockingQueue<>(100)
4);
5
6// 动态修改核心线程数
7executor.setCorePoolSize(10);
8
9// 获取当前核心线程数
10int coreSize = executor.getCorePoolSize();

修改最大线程数:

java
1executor.setMaximumPoolSize(20);

修改空闲线程存活时间:

java
1executor.setKeepAliveTime(120L, TimeUnit.SECONDS);

注意事项:

  1. 增加核心线程数:会立即创建新线程(如果有任务在等待)
  2. 减少核心线程数:多余的核心线程会在空闲后被回收
  3. 最大线程数必须 >= 核心线程数:否则抛出 IllegalArgumentException

动态调整场景示例:

java
1public class DynamicThreadPool {
2 private ThreadPoolExecutor executor;
3
4 public void adjustByLoad() {
5 int queueSize = executor.getQueue().size();
6 int activeCount = executor.getActiveCount();
7
8 // 根据队列大小和活跃线程数动态调整
9 if (queueSize > 100 && activeCount < executor.getMaximumPoolSize()) {
10 executor.setCorePoolSize(executor.getCorePoolSize() + 5);
11 } else if (queueSize < 10 && executor.getCorePoolSize() > 5) {
12 executor.setCorePoolSize(executor.getCorePoolSize() - 5);
13 }
14 }
15}

监控线程池状态:

java
1// 当前线程池大小
2int poolSize = executor.getPoolSize();
3
4// 活跃线程数
5int activeCount = executor.getActiveCount();
6
7// 队列中任务数
8int queueSize = executor.getQueue().size();
9
10// 已完成任务数
11long completedTaskCount = executor.getCompletedTaskCount();
12
13// 总任务数
14long taskCount = executor.getTaskCount();

12. 线程池中 shutdown 与 shutdownNow 的区别是什么?

答案:

shutdown() 方法:

  • 温和关闭,不接受新任务
  • 继续执行队列中的任务
  • 等待所有任务执行完成
  • 不会中断正在执行的任务
java
1executor.shutdown();
2
3// 等待所有任务完成
4try {
5 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
6 executor.shutdownNow(); // 超时后强制关闭
7 }
8} catch (InterruptedException e) {
9 executor.shutdownNow();
10}

shutdownNow() 方法:

  • 立即关闭,不接受新任务
  • 尝试停止所有正在执行的任务(发送中断信号)
  • 不再执行队列中等待的任务
  • 返回等待执行的任务列表
java
1List<Runnable> notExecutedTasks = executor.shutdownNow();
2System.out.println("未执行的任务数: " + notExecutedTasks.size());

对比表:

特性shutdown()shutdownNow()
接受新任务
执行队列任务
中断运行任务是(发送中断)
返回值voidList<Runnable>
关闭速度慢(等待完成)快(立即停止)

完整关闭示例:

java
1public void shutdownGracefully(ExecutorService executor) {
2 executor.shutdown(); // 禁止新任务提交
3
4 try {
5 // 等待 60 秒让任务完成
6 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
7 // 超时后强制关闭
8 List<Runnable> droppedTasks = executor.shutdownNow();
9 System.out.println("强制关闭,丢弃任务: " + droppedTasks.size());
10
11 // 再等待 60 秒
12 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
13 System.err.println("线程池未能正常关闭");
14 }
15 }
16 } catch (InterruptedException e) {
17 // 当前线程被中断,强制关闭线程池
18 executor.shutdownNow();
19 Thread.currentThread().interrupt();
20 }
21}

注意事项:

  1. shutdownNow() 不保证能停止正在执行的任务(任务需要响应中断)
  2. 任务应该正确处理 InterruptedException
  3. 使用 awaitTermination() 等待线程池真正终止

13. 线程池内部任务出异常后,如何知道是哪个线程出了异常?

答案:

线程池任务异常处理有多种方式:

1. 使用 Future.get() 捕获异常

java
1ExecutorService executor = Executors.newFixedThreadPool(5);
2
3Future<?> future = executor.submit(() -> {
4 throw new RuntimeException("任务异常");
5});
6
7try {
8 future.get(); // 会抛出 ExecutionException
9} catch (ExecutionException e) {
10 Throwable cause = e.getCause();
11 System.out.println("任务异常: " + cause.getMessage());
12}

2. 在任务内部捕获异常

java
1executor.submit(() -> {
2 try {
3 // 任务逻辑
4 int result = 1 / 0;
5 } catch (Exception e) {
6 System.out.println("线程: " + Thread.currentThread().getName()
7 + " 异常: " + e.getMessage());
8 // 记录日志或上报
9 }
10});

3. 自定义 ThreadFactory 设置 UncaughtExceptionHandler

java
1ThreadFactory factory = new ThreadFactory() {
2 private AtomicInteger threadNumber = new AtomicInteger(1);
3
4 @Override
5 public Thread newThread(Runnable r) {
6 Thread thread = new Thread(r);
7 thread.setName("MyPool-" + threadNumber.getAndIncrement());
8
9 // 设置未捕获异常处理器
10 thread.setUncaughtExceptionHandler((t, e) -> {
11 System.out.println("线程 " + t.getName() + " 异常: " + e.getMessage());
12 // 记录日志、发送告警
13 });
14
15 return thread;
16 }
17};
18
19ThreadPoolExecutor executor = new ThreadPoolExecutor(
20 5, 10, 60L, TimeUnit.SECONDS,
21 new LinkedBlockingQueue<>(),
22 factory
23);

4. 重写 ThreadPoolExecutor.afterExecute() 方法

java
1public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
2
3 public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
4 long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
5 super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
6 }
7
8 @Override
9 protected void afterExecute(Runnable r, Throwable t) {
10 super.afterExecute(r, t);
11
12 if (t == null && r instanceof Future<?>) {
13 try {
14 Future<?> future = (Future<?>) r;
15 if (future.isDone()) {
16 future.get();
17 }
18 } catch (ExecutionException e) {
19 t = e.getCause();
20 } catch (InterruptedException e) {
21 Thread.currentThread().interrupt();
22 }
23 }
24
25 if (t != null) {
26 System.out.println("任务执行异常: " + t.getMessage());
27 // 记录日志、发送告警
28 }
29 }
30}

5. 使用 CompletableFuture 处理异常

java
1CompletableFuture.supplyAsync(() -> {
2 // 任务逻辑
3 return 1 / 0;
4}, executor).exceptionally(ex -> {
5 System.out.println("异常处理: " + ex.getMessage());
6 return null;
7});

最佳实践:

  • submit() 提交的任务使用 Future.get() 获取异常
  • execute() 提交的任务使用 UncaughtExceptionHandler
  • 重要任务在内部 try-catch 处理异常
  • 记录详细的异常信息(线程名、时间、堆栈)

14. Java 中的 DelayQueue 和 ScheduledThreadPool 有什么区别?

答案:

DelayQueue:

  • 是一个阻塞队列,存储 Delayed 元素
  • 只有到期的元素才能被取出
  • 需要手动从队列中取元素并处理
  • 更灵活,可以自定义处理逻辑
java
1public class DelayedTask implements Delayed {
2 private String name;
3 private long delayTime; // 延迟时间(毫秒)
4 private long expire; // 到期时间
5
6 public DelayedTask(String name, long delayTime) {
7 this.name = name;
8 this.delayTime = delayTime;
9 this.expire = System.currentTimeMillis() + delayTime;
10 }
11
12 @Override
13 public long getDelay(TimeUnit unit) {
14 return unit.convert(expire - System.currentTimeMillis(),
15 TimeUnit.MILLISECONDS);
16 }
17
18 @Override
19 public int compareTo(Delayed o) {
20 return Long.compare(this.expire, ((DelayedTask) o).expire);
21 }
22}
23
24// 使用
25DelayQueue<DelayedTask> queue = new DelayQueue<>();
26queue.put(new DelayedTask("task1", 5000)); // 5秒后到期
27
28// 手动取出并处理
29DelayedTask task = queue.take(); // 阻塞直到有元素到期
30System.out.println("执行任务: " + task.getName());

ScheduledThreadPool:

  • 是一个线程池,专门用于执行定时任务
  • 自动调度和执行任务
  • 支持固定延迟和固定频率执行
  • 内部使用 DelayedWorkQueue
java
1ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
2
3// 延迟执行(一次性)
4scheduler.schedule(() -> {
5 System.out.println("5秒后执行");
6}, 5, TimeUnit.SECONDS);
7
8// 固定频率执行(周期性)
9scheduler.scheduleAtFixedRate(() -> {
10 System.out.println("每秒执行一次");
11}, 0, 1, TimeUnit.SECONDS);
12
13// 固定延迟执行(周期性)
14scheduler.scheduleWithFixedDelay(() -> {
15 System.out.println("上次执行完成后延迟1秒再执行");
16}, 0, 1, TimeUnit.SECONDS);

主要区别:

特性DelayQueueScheduledThreadPool
类型阻塞队列线程池
自动执行否,需手动取出是,自动调度执行
周期任务不支持支持
线程管理需自己管理自动管理
使用复杂度较高较低
灵活性
适用场景自定义调度逻辑标准定时任务

scheduleAtFixedRate vs scheduleWithFixedDelay:

java
1// scheduleAtFixedRate: 固定频率
2// 如果任务执行时间 < 间隔时间,按固定频率执行
3// 如果任务执行时间 > 间隔时间,任务执行完立即开始下一次
4scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
5// 0s -> 1s -> 2s -> 3s (理想情况)
6
7// scheduleWithFixedDelay: 固定延迟
8// 上次任务执行完成后,延迟指定时间再执行
9scheduler.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);
10// 0s -> 任务完成(0.5s) -> 1.5s -> 任务完成(2s) -> 3s

选择建议:

  • 简单定时任务:使用 ScheduledThreadPool
  • 复杂调度逻辑:使用 DelayQueue
  • 需要周期执行:使用 ScheduledThreadPool
  • 需要动态调整延迟:使用 DelayQueue

15. 什么是 Java 的 Timer?

答案:

Timer 是 Java 早期提供的定时任务工具类,使用单线程执行所有定时任务。

java
1Timer timer = new Timer();
2
3// 延迟执行
4timer.schedule(new TimerTask() {
5 @Override
6 public void run() {
7 System.out.println("5秒后执行");
8 }
9}, 5000);
10
11// 周期执行
12timer.scheduleAtFixedRate(new TimerTask() {
13 @Override
14 public void run() {
15 System.out.println("每秒执行");
16 }
17}, 0, 1000);

Timer 的缺点:

  1. 单线程执行:所有任务串行执行,一个任务延迟会影响其他任务
  2. 异常处理差:任务抛出异常会导致整个 Timer 终止
  3. 不支持绝对时间:基于相对时间,系统时间改变会影响调度
  4. 功能有限:不支持线程池、返回值等
java
1// 问题示例:一个任务异常导致 Timer 终止
2Timer timer = new Timer();
3timer.schedule(new TimerTask() {
4 @Override
5 public void run() {
6 throw new RuntimeException("异常");
7 }
8}, 1000);
9
10timer.schedule(new TimerTask() {
11 @Override
12 public void run() {
13 System.out.println("这个任务不会执行");
14 }
15}, 2000);

替代方案:

  • 使用 ScheduledExecutorService(推荐)
  • 使用 Quartz 等专业调度框架
  • 使用 Spring 的 @Scheduled 注解

16. 你了解时间轮(Time Wheel)吗?有哪些应用场景?

答案:

时间轮(Time Wheel) 是一种高效的定时器算法,用于管理大量定时任务。

原理:

  • 类似时钟,将时间分成多个槽位(slot)
  • 每个槽位存储该时间点要执行的任务
  • 指针按固定频率移动,执行当前槽位的任务
  • 支持多层时间轮(秒、分、时)
1时间轮示意图:
2 0
3 7 1
46 2
5 5 3
6 4
7
8每个槽位存储任务链表
9指针每秒移动一格

简单实现:

java
1public class SimpleTimeWheel {
2 private List<Set<Task>> wheel;
3 private int tickDuration; // 每格时间(毫秒)
4 private int wheelSize; // 轮子大小
5 private int currentTick; // 当前指针位置
6
7 public SimpleTimeWheel(int tickDuration, int wheelSize) {
8 this.tickDuration = tickDuration;
9 this.wheelSize = wheelSize;
10 this.wheel = new ArrayList<>(wheelSize);
11 for (int i = 0; i < wheelSize; i++) {
12 wheel.add(new HashSet<>());
13 }
14 }
15
16 public void addTask(Task task, long delayMs) {
17 int ticks = (int) (delayMs / tickDuration);
18 int index = (currentTick + ticks) % wheelSize;
19 wheel.get(index).add(task);
20 }
21
22 public void tick() {
23 Set<Task> tasks = wheel.get(currentTick);
24 tasks.forEach(Task::run);
25 tasks.clear();
26 currentTick = (currentTick + 1) % wheelSize;
27 }
28}

优点:

  • 添加/删除任务时间复杂度 O(1)
  • 适合大量定时任务
  • 内存占用可控

缺点:

  • 精度受限于 tick 间隔
  • 不适合长时间延迟(需要多层时间轮)

应用场景:

  1. Netty 的 HashedWheelTimer
java
1HashedWheelTimer timer = new HashedWheelTimer(
2 100, TimeUnit.MILLISECONDS, // tick 间隔
3 512 // 轮子大小
4);
5
6timer.newTimeout(timeout -> {
7 System.out.println("任务执行");
8}, 5, TimeUnit.SECONDS);
  1. Kafka 的延迟操作
  2. Dubbo 的超时检测
  3. 连接超时管理
  4. 会话过期检测

与其他定时器对比:

方案添加任务删除任务适用场景
TimerO(log n)O(n)少量任务
ScheduledThreadPoolO(log n)O(log n)中等任务
时间轮O(1)O(1)大量任务

17. 你使用过哪些 Java 并发工具类?

答案:

Java 并发包(java.util.concurrent)提供了丰富的并发工具类:

1. 同步工具类

CountDownLatch(倒计数门栓)

java
1CountDownLatch latch = new CountDownLatch(3);
2
3// 工作线程
4new Thread(() -> {
5 System.out.println("任务1完成");
6 latch.countDown();
7}).start();
8
9// 主线程等待
10latch.await(); // 等待计数归零
11System.out.println("所有任务完成");

CyclicBarrier(循环栅栏)

java
1CyclicBarrier barrier = new CyclicBarrier(3, () -> {
2 System.out.println("所有线程到达屏障");
3});
4
5// 每个线程
6barrier.await(); // 等待其他线程

Semaphore(信号量)

java
1Semaphore semaphore = new Semaphore(3); // 3个许可
2
3semaphore.acquire(); // 获取许可
4try {
5 // 执行任务
6} finally {
7 semaphore.release(); // 释放许可
8}

Exchanger(交换器)

java
1Exchanger<String> exchanger = new Exchanger<>();
2
3// 线程1
4String data1 = exchanger.exchange("数据1");
5
6// 线程2
7String data2 = exchanger.exchange("数据2");

2. 并发容器

java
1// 线程安全的 Map
2ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
3
4// 线程安全的 List(读多写少)
5CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
6
7// 线程安全的 Set
8CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
9
10// 阻塞队列
11BlockingQueue<String> queue = new LinkedBlockingQueue<>();

3. 原子类

java
1// 原子整数
2AtomicInteger count = new AtomicInteger(0);
3count.incrementAndGet();
4
5// 原子引用
6AtomicReference<User> userRef = new AtomicReference<>();
7userRef.compareAndSet(oldUser, newUser);
8
9// 原子数组
10AtomicIntegerArray array = new AtomicIntegerArray(10);
11
12// 原子字段更新器
13AtomicIntegerFieldUpdater<User> updater =
14 AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

4. 锁

java
1// 可重入锁
2ReentrantLock lock = new ReentrantLock();
3
4// 读写锁
5ReadWriteLock rwLock = new ReentrantReadWriteLock();
6
7// 邮戳锁(Java 8+)
8StampedLock stampedLock = new StampedLock();

5. 异步编程

java
1// CompletableFuture
2CompletableFuture<String> future = CompletableFuture
3 .supplyAsync(() -> "Hello")
4 .thenApply(s -> s + " World");
5
6// ForkJoinPool
7ForkJoinPool pool = new ForkJoinPool();

6. 线程池

java
1// 标准线程池
2ExecutorService executor = Executors.newFixedThreadPool(5);
3
4// 定时线程池
5ScheduledExecutorService scheduler =
6 Executors.newScheduledThreadPool(5);

常用场景总结:

  • 等待多个任务完成:CountDownLatch
  • 多线程协同工作:CyclicBarrier
  • 限流控制:Semaphore
  • 线程安全集合:ConcurrentHashMap、CopyOnWriteArrayList
  • 原子操作:AtomicInteger、AtomicReference
  • 异步编程:CompletableFuture

18. 什么是 Java 的 Semaphore?

答案:

Semaphore(信号量)是一个计数信号量,用于控制同时访问特定资源的线程数量。

核心方法:

  • acquire():获取许可,如果没有可用许可则阻塞
  • release():释放许可
  • tryAcquire():尝试获取许可,立即返回
  • availablePermits():返回可用许可数

基本使用:

java
1// 创建3个许可的信号量
2Semaphore semaphore = new Semaphore(3);
3
4public void accessResource() {
5 try {
6 semaphore.acquire(); // 获取许可
7 System.out.println(Thread.currentThread().getName() + " 访问资源");
8 Thread.sleep(2000);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 } finally {
12 semaphore.release(); // 释放许可
13 }
14}

应用场景:

1. 限流控制

java
1public class RateLimiter {
2 private Semaphore semaphore;
3
4 public RateLimiter(int maxConcurrent) {
5 this.semaphore = new Semaphore(maxConcurrent);
6 }
7
8 public void execute(Runnable task) {
9 try {
10 semaphore.acquire();
11 task.run();
12 } catch (InterruptedException e) {
13 Thread.currentThread().interrupt();
14 } finally {
15 semaphore.release();
16 }
17 }
18}

2. 数据库连接池

java
1public class ConnectionPool {
2 private Semaphore semaphore;
3 private List<Connection> connections;
4
5 public ConnectionPool(int size) {
6 this.semaphore = new Semaphore(size);
7 this.connections = new ArrayList<>(size);
8 // 初始化连接
9 }
10
11 public Connection getConnection() throws InterruptedException {
12 semaphore.acquire();
13 return connections.remove(0);
14 }
15
16 public void releaseConnection(Connection conn) {
17 connections.add(conn);
18 semaphore.release();
19 }
20}

公平性:

java
1// 非公平模式(默认):性能更好
2Semaphore semaphore = new Semaphore(3);
3
4// 公平模式:按照请求顺序获取许可
5Semaphore fairSemaphore = new Semaphore(3, true);

19. 什么是 Java 的 CyclicBarrier?

答案:

CyclicBarrier(循环栅栏)是一个同步辅助类,让一组线程相互等待,直到所有线程都到达某个公共屏障点。

特点:

  • 可循环使用(Cyclic)
  • 所有线程到达屏障点后,可以执行一个可选的屏障动作
  • 适用于多线程协同工作的场景

基本使用:

java
1CyclicBarrier barrier = new CyclicBarrier(3, () -> {
2 System.out.println("所有线程都到达屏障,执行汇总操作");
3});
4
5// 每个线程
6public void doWork() {
7 try {
8 System.out.println(Thread.currentThread().getName() + " 开始工作");
9 Thread.sleep(1000);
10 System.out.println(Thread.currentThread().getName() + " 完成工作,等待其他线程");
11
12 barrier.await(); // 等待其他线程
13
14 System.out.println(Thread.currentThread().getName() + " 继续执行");
15 } catch (InterruptedException | BrokenBarrierException e) {
16 e.printStackTrace();
17 }
18}

应用场景:

1. 多线程计算后汇总

java
1public class ParallelCalculation {
2 private CyclicBarrier barrier;
3 private int[] results;
4
5 public ParallelCalculation(int threadCount) {
6 this.results = new int[threadCount];
7 this.barrier = new CyclicBarrier(threadCount, () -> {
8 // 所有线程计算完成,汇总结果
9 int sum = Arrays.stream(results).sum();
10 System.out.println("总和: " + sum);
11 });
12 }
13
14 public void calculate(int index) {
15 try {
16 // 执行计算
17 results[index] = index * 10;
18
19 // 等待其他线程
20 barrier.await();
21 } catch (Exception e) {
22 e.printStackTrace();
23 }
24 }
25}

2. 多阶段任务

java
1CyclicBarrier barrier = new CyclicBarrier(3);
2
3for (int phase = 0; phase < 3; phase++) {
4 final int currentPhase = phase;
5 for (int i = 0; i < 3; i++) {
6 new Thread(() -> {
7 System.out.println("阶段" + currentPhase + " 执行");
8 try {
9 barrier.await(); // 等待本阶段所有线程完成
10 } catch (Exception e) {
11 e.printStackTrace();
12 }
13 }).start();
14 }
15}

重要方法:

java
1// 等待其他线程
2barrier.await();
3
4// 带超时的等待
5barrier.await(10, TimeUnit.SECONDS);
6
7// 重置屏障
8barrier.reset();
9
10// 获取等待线程数
11int waiting = barrier.getNumberWaiting();
12
13// 检查屏障是否被破坏
14boolean broken = barrier.isBroken();

20. 什么是 Java 的 CountDownLatch?

答案:

CountDownLatch(倒计数门栓)是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。

特点:

  • 基于计数器,初始化时设置计数值
  • 调用 countDown() 减少计数
  • 调用 await() 阻塞直到计数归零
  • 一次性使用,不能重置

基本使用:

java
1CountDownLatch latch = new CountDownLatch(3);
2
3// 工作线程
4for (int i = 0; i < 3; i++) {
5 new Thread(() -> {
6 System.out.println(Thread.currentThread().getName() + " 执行任务");
7 try {
8 Thread.sleep(1000);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 latch.countDown(); // 计数减1
13 }).start();
14}
15
16// 主线程等待
17latch.await(); // 阻塞直到计数归零
18System.out.println("所有任务完成");

应用场景:

1. 等待多个线程完成初始化

java
1public class ApplicationStartup {
2 private CountDownLatch latch;
3
4 public void start() throws InterruptedException {
5 latch = new CountDownLatch(3);
6
7 // 初始化数据库
8 new Thread(() -> {
9 System.out.println("初始化数据库");
10 latch.countDown();
11 }).start();
12
13 // 初始化缓存
14 new Thread(() -> {
15 System.out.println("初始化缓存");
16 latch.countDown();
17 }).start();
18
19 // 初始化配置
20 new Thread(() -> {
21 System.out.println("初始化配置");
22 latch.countDown();
23 }).start();
24
25 // 等待所有初始化完成
26 latch.await();
27 System.out.println("应用启动完成");
28 }
29}

2. 并行任务执行

java
1public class ParallelTask {
2 public void executeParallel(List<Runnable> tasks) throws InterruptedException {
3 CountDownLatch latch = new CountDownLatch(tasks.size());
4
5 for (Runnable task : tasks) {
6 new Thread(() -> {
7 try {
8 task.run();
9 } finally {
10 latch.countDown();
11 }
12 }).start();
13 }
14
15 latch.await(); // 等待所有任务完成
16 }
17}

3. 模拟并发测试

java
1public class ConcurrencyTest {
2 public void testConcurrency(int threadCount) throws InterruptedException {
3 CountDownLatch startLatch = new CountDownLatch(1);
4 CountDownLatch endLatch = new CountDownLatch(threadCount);
5
6 for (int i = 0; i < threadCount; i++) {
7 new Thread(() -> {
8 try {
9 startLatch.await(); // 等待开始信号
10 // 执行测试
11 System.out.println("执行测试");
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 } finally {
15 endLatch.countDown();
16 }
17 }).start();
18 }
19
20 startLatch.countDown(); // 发出开始信号,所有线程同时开始
21 endLatch.await(); // 等待所有线程完成
22 System.out.println("测试完成");
23 }
24}

CountDownLatch vs CyclicBarrier:

特性CountDownLatchCyclicBarrier
可重用性不可重用可重用
等待方式一个或多个线程等待所有线程相互等待
计数方式递减到0递增到N
屏障动作
使用场景主线程等待子线程多线程协同

21. 什么是 Java 的 StampedLock?

22. 什么是 Java 的 CompletableFuture?

23. 什么是 Java 的 ForkJoinPool?

24. 如何在 Java 中控制多个线程的执行顺序?

25. 你使用过 Java 中的哪些阻塞队列?

26. 你使用过 Java 中的哪些原子类?

答案:

Java 提供了 java.util.concurrent.atomic 包,包含多种原子类,保证操作的原子性。

1. 基本类型原子类

java
1// AtomicInteger
2AtomicInteger count = new AtomicInteger(0);
3count.incrementAndGet(); // i++
4count.getAndIncrement(); // ++i
5count.addAndGet(5); // i += 5
6count.compareAndSet(0, 1); // CAS操作
7
8// AtomicLong
9AtomicLong longValue = new AtomicLong(0L);
10
11// AtomicBoolean
12AtomicBoolean flag = new AtomicBoolean(false);
13flag.compareAndSet(false, true);

2. 数组类型原子类

java
1// AtomicIntegerArray
2AtomicIntegerArray array = new AtomicIntegerArray(10);
3array.incrementAndGet(0); // 数组索引0的元素+1
4array.compareAndSet(0, 1, 2); // CAS操作
5
6// AtomicLongArray
7AtomicLongArray longArray = new AtomicLongArray(10);
8
9// AtomicReferenceArray
10AtomicReferenceArray<String> refArray = new AtomicReferenceArray<>(10);

3. 引用类型原子类

java
1// AtomicReference
2AtomicReference<User> userRef = new AtomicReference<>();
3User oldUser = new User("张三");
4User newUser = new User("李四");
5userRef.compareAndSet(oldUser, newUser);
6
7// AtomicStampedReference(解决ABA问题)
8AtomicStampedReference<String> stampedRef =
9 new AtomicStampedReference<>("初始值", 0);
10int stamp = stampedRef.getStamp();
11stampedRef.compareAndSet("初始值", "新值", stamp, stamp + 1);
12
13// AtomicMarkableReference(标记是否被修改过)
14AtomicMarkableReference<String> markableRef =
15 new AtomicMarkableReference<>("初始值", false);
16markableRef.compareAndSet("初始值", "新值", false, true);

4. 字段更新器

java
1class User {
2 volatile int age;
3 volatile String name;
4}
5
6// AtomicIntegerFieldUpdater
7AtomicIntegerFieldUpdater<User> ageUpdater =
8 AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
9User user = new User();
10ageUpdater.incrementAndGet(user);
11
12// AtomicReferenceFieldUpdater
13AtomicReferenceFieldUpdater<User, String> nameUpdater =
14 AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
15nameUpdater.compareAndSet(user, "旧名字", "新名字");

应用场景示例:

java
1// 1. 计数器
2public class Counter {
3 private AtomicInteger count = new AtomicInteger(0);
4
5 public void increment() {
6 count.incrementAndGet();
7 }
8
9 public int getCount() {
10 return count.get();
11 }
12}
13
14// 2. 单例模式
15public class Singleton {
16 private static final AtomicReference<Singleton> INSTANCE =
17 new AtomicReference<>();
18
19 public static Singleton getInstance() {
20 while (true) {
21 Singleton current = INSTANCE.get();
22 if (current != null) {
23 return current;
24 }
25 current = new Singleton();
26 if (INSTANCE.compareAndSet(null, current)) {
27 return current;
28 }
29 }
30 }
31}

27. 你使用过 Java 的累加器吗?

答案:

Java 8 引入了累加器(Accumulator),在高并发场景下性能优于原子类。

1. LongAdder(长整型累加器)

java
1LongAdder adder = new LongAdder();
2
3// 多线程累加
4for (int i = 0; i < 10; i++) {
5 new Thread(() -> {
6 for (int j = 0; j < 1000; j++) {
7 adder.increment(); // 加1
8 adder.add(5); // 加5
9 }
10 }).start();
11}
12
13// 获取结果
14long sum = adder.sum();

2. LongAccumulator(长整型累积器)

java
1// 自定义累积函数(求最大值)
2LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
3
4accumulator.accumulate(100);
5accumulator.accumulate(200);
6accumulator.accumulate(50);
7
8long max = accumulator.get(); // 200

3. DoubleAdder 和 DoubleAccumulator

java
1DoubleAdder doubleAdder = new DoubleAdder();
2doubleAdder.add(1.5);
3double sum = doubleAdder.sum();
4
5DoubleAccumulator doubleAccumulator =
6 new DoubleAccumulator(Double::sum, 0.0);

性能对比:

java
1// AtomicLong vs LongAdder 性能测试
2public class PerformanceTest {
3 public static void testAtomicLong() {
4 AtomicLong atomicLong = new AtomicLong(0);
5 long start = System.currentTimeMillis();
6
7 // 10个线程,每个累加100万次
8 for (int i = 0; i < 10; i++) {
9 new Thread(() -> {
10 for (int j = 0; j < 1000000; j++) {
11 atomicLong.incrementAndGet();
12 }
13 }).start();
14 }
15
16 // AtomicLong: 约2000ms
17 }
18
19 public static void testLongAdder() {
20 LongAdder longAdder = new LongAdder();
21 long start = System.currentTimeMillis();
22
23 for (int i = 0; i < 10; i++) {
24 new Thread(() -> {
25 for (int j = 0; j < 1000000; j++) {
26 longAdder.increment();
27 }
28 }).start();
29 }
30
31 // LongAdder: 约500ms(快4倍)
32 }
33}

原理:

  • AtomicLong:所有线程竞争同一个变量,CAS 冲突多
  • LongAdder:每个线程维护自己的计数器(Cell),最后汇总,减少竞争

内部结构:

1LongAdder
2├── base (基础值)
3└── cells[] (Cell数组)
4 ├── Cell[0] (线程1的计数)
5 ├── Cell[1] (线程2的计数)
6 └── Cell[2] (线程3的计数)
7
8sum() = base + Cell[0] + Cell[1] + Cell[2] + ...

选择建议:

  • 低并发场景:AtomicLong(更简单)
  • 高并发累加:LongAdder(性能更好)
  • 需要 CAS 操作:AtomicLong
  • 只需要累加:LongAdder

28. 什么是 Java 的 CAS(Compare-And-Swap)操作?

答案:

CAS(Compare-And-Swap)是一种无锁的原子操作,用于实现并发控制。

原理: CAS 包含三个操作数:

  • V:内存位置(变量)
  • E:预期值(Expected)
  • N:新值(New)

只有当 V 的值等于 E 时,才将 V 的值设置为 N,否则不做任何操作。整个过程是原子的。

伪代码:

java
1boolean compareAndSwap(Variable V, int E, int N) {
2 if (V == E) {
3 V = N;
4 return true;
5 }
6 return false;
7}

Java 中的 CAS:

java
1AtomicInteger count = new AtomicInteger(0);
2
3// CAS 操作
4boolean success = count.compareAndSet(0, 1);
5// 如果当前值是0,则设置为1,返回true
6// 如果当前值不是0,不做任何操作,返回false
7
8// 自旋 CAS
9public void increment() {
10 int oldValue;
11 int newValue;
12 do {
13 oldValue = count.get();
14 newValue = oldValue + 1;
15 } while (!count.compareAndSet(oldValue, newValue));
16}

底层实现:

java
1// Unsafe 类提供的 CAS 方法
2public final native boolean compareAndSwapInt(
3 Object o, // 对象
4 long offset, // 字段偏移量
5 int expected, // 预期值
6 int x // 新值
7);
8
9// CPU 指令(x86)
10lock cmpxchg [内存地址], 新值

CAS 的优点:

  1. 无锁,避免线程阻塞和上下文切换
  2. 性能高于 synchronized
  3. 适合低并发场景

CAS 的缺点:

1. ABA 问题

java
1// 线程1:读取值A
2int value = atomicInt.get(); // A
3
4// 线程2:A -> B -> A
5atomicInt.compareAndSet(A, B);
6atomicInt.compareAndSet(B, A);
7
8// 线程1:CAS成功,但值已经被修改过
9atomicInt.compareAndSet(A, C); // 成功,但不知道中间变化
10
11// 解决方案:使用版本号
12AtomicStampedReference<Integer> stampedRef =
13 new AtomicStampedReference<>(A, 0);
14int stamp = stampedRef.getStamp();
15stampedRef.compareAndSet(A, C, stamp, stamp + 1);

2. 循环时间长开销大

java
1// 高并发下,CAS 失败次数多,CPU 开销大
2public void increment() {
3 int oldValue;
4 int newValue;
5 do {
6 oldValue = count.get();
7 newValue = oldValue + 1;
8 // 高并发下可能循环很多次
9 } while (!count.compareAndSet(oldValue, newValue));
10}
11
12// 解决方案:使用 LongAdder 或加锁

3. 只能保证一个共享变量的原子操作

java
1// 不能同时 CAS 多个变量
2// 解决方案:
3// 1. 使用 AtomicReference 包装多个变量
4class Pair {
5 int a;
6 int b;
7}
8AtomicReference<Pair> pairRef = new AtomicReference<>(new Pair());
9
10// 2. 使用锁
11synchronized (lock) {
12 a++;
13 b++;
14}

应用场景:

  • 原子类(AtomicInteger、AtomicReference)
  • 并发容器(ConcurrentHashMap)
  • AQS(AbstractQueuedSynchronizer)
  • 乐观锁实现

29. 说说 AQS 吧?

答案:

AQS(AbstractQueuedSynchronizer)是 Java 并发包的核心基础框架,用于构建锁和同步器。

核心思想:

  • 使用一个 int 类型的 state 变量表示同步状态
  • 使用 FIFO 队列管理等待线程
  • 提供独占模式和共享模式

核心组件:

1. 同步状态(state)

java
1private volatile int state;
2
3// 获取状态
4protected final int getState()
5
6// 设置状态
7protected final void setState(int newState)
8
9// CAS 设置状态
10protected final boolean compareAndSetState(int expect, int update)

2. 等待队列(CLH 队列)

1head -> Node1 -> Node2 -> Node3 -> tail
2 (等待) (等待) (等待)

每个 Node 包含:

  • thread:等待的线程
  • waitStatus:等待状态
  • prev/next:前驱/后继节点

工作流程:

独占模式(如 ReentrantLock):

java
1// 1. 尝试获取锁
2public final void acquire(int arg) {
3 if (!tryAcquire(arg) && // 尝试获取(子类实现)
4 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { // 加入队列等待
5 selfInterrupt();
6 }
7}
8
9// 2. 释放锁
10public final boolean release(int arg) {
11 if (tryRelease(arg)) { // 尝试释放(子类实现)
12 Node h = head;
13 if (h != null && h.waitStatus != 0)
14 unparkSuccessor(h); // 唤醒后继节点
15 return true;
16 }
17 return false;
18}

共享模式(如 Semaphore、CountDownLatch):

java
1// 获取共享锁
2public final void acquireShared(int arg) {
3 if (tryAcquireShared(arg) < 0) // 尝试获取
4 doAcquireShared(arg); // 加入队列等待
5}
6
7// 释放共享锁
8public final boolean releaseShared(int arg) {
9 if (tryReleaseShared(arg)) { // 尝试释放
10 doReleaseShared(); // 唤醒后继节点
11 return true;
12 }
13 return false;
14}

自定义同步器示例:

java
1// 自定义互斥锁
2public class Mutex implements Lock {
3 private static class Sync extends AbstractQueuedSynchronizer {
4 // 尝试获取锁
5 @Override
6 protected boolean tryAcquire(int arg) {
7 return compareAndSetState(0, 1);
8 }
9
10 // 尝试释放锁
11 @Override
12 protected boolean tryRelease(int arg) {
13 setState(0);
14 return true;
15 }
16
17 // 是否被独占
18 @Override
19 protected boolean isHeldExclusively() {
20 return getState() == 1;
21 }
22
23 Condition newCondition() {
24 return new ConditionObject();
25 }
26 }
27
28 private final Sync sync = new Sync();
29
30 @Override
31 public void lock() {
32 sync.acquire(1);
33 }
34
35 @Override
36 public void unlock() {
37 sync.release(1);
38 }
39
40 // 其他方法实现...
41}

基于 AQS 的同步器:

  • ReentrantLock:独占锁
  • ReentrantReadWriteLock:读写锁
  • Semaphore:信号量
  • CountDownLatch:倒计数门栓
  • CyclicBarrier:循环栅栏(基于 ReentrantLock)

AQS 的优势:

  1. 提供统一的框架,简化同步器实现
  2. 高效的等待队列管理
  3. 支持可中断、超时、公平/非公平
  4. 提供 Condition 支持

30. Java 中 ReentrantLock 的实现原理是什么?

答案:

ReentrantLock 是基于 AQS 实现的可重入独占锁。

核心特性:

  1. 可重入:同一线程可以多次获取锁
  2. 公平/非公平:支持两种模式
  3. 可中断:支持中断等待
  4. 可超时:支持超时获取锁
  5. Condition 支持:支持多个条件变量

内部结构:

java
1public class ReentrantLock implements Lock {
2 private final Sync sync;
3
4 // 抽象同步器
5 abstract static class Sync extends AbstractQueuedSynchronizer {
6 // state 表示重入次数
7 }
8
9 // 非公平同步器
10 static final class NonfairSync extends Sync {
11 final void lock() {
12 // 直接尝试 CAS 获取锁
13 if (compareAndSetState(0, 1))
14 setExclusiveOwnerThread(Thread.currentThread());
15 else
16 acquire(1);
17 }
18 }
19
20 // 公平同步器
21 static final class FairSync extends Sync {
22 final void lock() {
23 // 必须排队
24 acquire(1);
25 }
26
27 protected final boolean tryAcquire(int acquires) {
28 // 检查队列中是否有等待线程
29 if (hasQueuedPredecessors())
30 return false;
31 // ...
32 }
33 }
34}

加锁流程:

非公平锁:

java
1public void lock() {
2 // 1. 直接尝试 CAS 获取锁
3 if (compareAndSetState(0, 1)) {
4 setExclusiveOwnerThread(Thread.currentThread());
5 return;
6 }
7
8 // 2. CAS 失败,调用 AQS 的 acquire
9 acquire(1);
10 // 2.1 tryAcquire:再次尝试获取
11 // 2.2 失败则加入等待队列
12 // 2.3 park 阻塞线程
13}

公平锁:

java
1public void lock() {
2 acquire(1);
3 // 1. tryAcquire:检查队列是否有等待线程
4 // 2. 有等待线程则直接失败
5 // 3. 没有等待线程则尝试 CAS 获取
6 // 4. 失败则加入队列等待
7}

重入实现:

java
1protected final boolean tryAcquire(int acquires) {
2 final Thread current = Thread.currentThread();
3 int c = getState();
4
5 if (c == 0) {
6 // 锁空闲,尝试获取
7 if (compareAndSetState(0, acquires)) {
8 setExclusiveOwnerThread(current);
9 return true;
10 }
11 } else if (current == getExclusiveOwnerThread()) {
12 // 当前线程已持有锁,重入
13 int nextc = c + acquires;
14 if (nextc < 0) // 溢出检查
15 throw new Error("Maximum lock count exceeded");
16 setState(nextc); // 增加重入次数
17 return true;
18 }
19 return false;
20}

解锁流程:

java
1public void unlock() {
2 release(1);
3}
4
5protected final boolean tryRelease(int releases) {
6 int c = getState() - releases;
7 if (Thread.currentThread() != getExclusiveOwnerThread())
8 throw new IllegalMonitorStateException();
9
10 boolean free = false;
11 if (c == 0) {
12 // 重入次数归零,完全释放锁
13 free = true;
14 setExclusiveOwnerThread(null);
15 }
16 setState(c); // 减少重入次数
17 return free;
18}

使用示例:

java
1ReentrantLock lock = new ReentrantLock();
2
3// 基本使用
4lock.lock();
5try {
6 // 临界区代码
7} finally {
8 lock.unlock();
9}
10
11// 可中断
12try {
13 lock.lockInterruptibly();
14 // 临界区代码
15} catch (InterruptedException e) {
16 // 处理中断
17} finally {
18 lock.unlock();
19}
20
21// 超时获取
22if (lock.tryLock(1, TimeUnit.SECONDS)) {
23 try {
24 // 临界区代码
25 } finally {
26 lock.unlock();
27 }
28} else {
29 // 获取锁失败
30}
31
32// Condition 使用
33Condition condition = lock.newCondition();
34lock.lock();
35try {
36 condition.await(); // 等待
37 condition.signal(); // 唤醒
38} finally {
39 lock.unlock();
40}

公平锁 vs 非公平锁:

特性公平锁非公平锁
获取顺序按排队顺序可插队
性能较低较高
吞吐量较低较高
线程切换更多更少
适用场景需要公平性追求性能

默认使用非公平锁:

java
1// 非公平锁(默认)
2ReentrantLock lock = new ReentrantLock();
3
4// 公平锁
5ReentrantLock fairLock = new ReentrantLock(true);

31. Java 的 synchronized 是怎么实现的?

答案:

synchronized 是 Java 提供的内置锁机制,基于对象监视器(Monitor)实现。

实现层次:

1. 字节码层面

java
1public void syncMethod() {
2 synchronized (this) {
3 // 临界区代码
4 }
5}
6
7// 字节码
8monitorenter // 进入监视器(获取锁)
9// 临界区代码
10monitorexit // 退出监视器(释放锁)
11monitorexit // 异常情况的释放

2. 对象头(Mark Word)

Java 对象在内存中的布局:

1|--------------------------------------------------------------|
2| Object Header (对象头) |
3|--------------------------------------------------------------|
4| Mark Word (标记字段,8字节) |
5| Class Pointer (类型指针,4/8字节) |
6|--------------------------------------------------------------|
7| Instance Data (实例数据) |
8|--------------------------------------------------------------|
9| Padding (对齐填充) |
10|--------------------------------------------------------------|

Mark Word 存储锁信息:

1|----------|---------------------------|
2| 锁状态 | Mark Word 内容 |
3|----------|---------------------------|
4| 无锁 | hashCode | age | 0 | 01 |
5| 偏向锁 | threadID | epoch | 1 | 01 |
6| 轻量级锁 | 栈中锁记录指针 | 00 |
7| 重量级锁 | Monitor 指针 | 10 |
8| GC标记 | 空 | 11 |
9|----------|---------------------------|

3. 锁升级过程

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

偏向锁(Biased Locking):

java
1// 第一次获取锁
2// 1. 检查 Mark Word 是否为可偏向状态
3// 2. CAS 将线程 ID 写入 Mark Word
4// 3. 成功则获取偏向锁
5
6// 再次获取锁(同一线程)
7// 1. 检查 Mark Word 中的线程 ID
8// 2. 如果是当前线程,直接执行(无需 CAS)
9
10// 其他线程竞争
11// 1. 撤销偏向锁
12// 2. 升级为轻量级锁

轻量级锁(Lightweight Locking):

java
1// 获取锁
2// 1. 在栈帧中创建锁记录(Lock Record)
3// 2. 复制 Mark Word 到锁记录
4// 3. CAS 将对象头的 Mark Word 替换为指向锁记录的指针
5// 4. 成功则获取轻量级锁
6// 5. 失败则自旋重试
7// 6. 自旋一定次数后升级为重量级锁
8
9// 释放锁
10// 1. CAS 将锁记录中的 Mark Word 替换回对象头
11// 2. 成功则释放锁
12// 3. 失败则升级为重量级锁

重量级锁(Heavyweight Locking):

java
1// 获取锁
2// 1. 对象头指向 Monitor 对象
3// 2. 线程进入 Monitor 的 EntryList
4// 3. 竞争 Monitor 的 owner
5// 4. 获取失败则阻塞(park)
6
7// Monitor 结构
8ObjectMonitor {
9 _owner; // 持有锁的线程
10 _EntryList; // 等待获取锁的线程队列
11 _WaitSet; // 调用 wait() 的线程队列
12 _count; // 重入次数
13}

完整流程图:

1对象创建
2
3无锁状态(001)
4 ↓ 线程1访问
5偏向锁(101)- 线程ID = 线程1
6 ↓ 线程2竞争
7轻量级锁(00)- 自旋获取
8 ↓ 自旋失败/竞争激烈
9重量级锁(10)- 阻塞等待

代码示例:

java
1public class SynchronizedDemo {
2 private Object lock = new Object();
3
4 // 同步代码块
5 public void method1() {
6 synchronized (lock) {
7 // 临界区
8 }
9 }
10
11 // 同步实例方法(锁是 this)
12 public synchronized void method2() {
13 // 临界区
14 }
15
16 // 同步静态方法(锁是 Class 对象)
17 public static synchronized void method3() {
18 // 临界区
19 }
20}

优化技术:

  1. 锁消除:JIT 编译器检测到不可能存在竞争,消除锁
  2. 锁粗化:多个连续的加锁解锁操作合并为一次
  3. 自适应自旋:根据历史自旋成功率动态调整自旋次数

32. Synchronized 修饰静态方法和修饰普通方法有什么区别?

答案:

主要区别在于锁的对象不同:

1. 修饰普通方法(实例方法)

java
1public class MyClass {
2 // 锁对象是 this(当前实例)
3 public synchronized void instanceMethod() {
4 // 临界区
5 }
6
7 // 等价于
8 public void instanceMethod2() {
9 synchronized (this) {
10 // 临界区
11 }
12 }
13}

特点:

  • 锁对象是当前实例(this)
  • 不同实例之间不互斥
  • 同一实例的多个同步方法互斥

示例:

java
1MyClass obj1 = new MyClass();
2MyClass obj2 = new MyClass();
3
4// 线程1和线程2不互斥(不同实例)
5new Thread(() -> obj1.instanceMethod()).start();
6new Thread(() -> obj2.instanceMethod()).start();
7
8// 线程3和线程4互斥(同一实例)
9new Thread(() -> obj1.instanceMethod()).start();
10new Thread(() -> obj1.instanceMethod()).start();

2. 修饰静态方法(类方法)

java
1public class MyClass {
2 // 锁对象是 MyClass.class(类对象)
3 public static synchronized void staticMethod() {
4 // 临界区
5 }
6
7 // 等价于
8 public static void staticMethod2() {
9 synchronized (MyClass.class) {
10 // 临界区
11 }
12 }
13}

特点:

  • 锁对象是 Class 对象(MyClass.class)
  • 所有实例共享同一个锁
  • 类的所有静态同步方法互斥

示例:

java
1MyClass obj1 = new MyClass();
2MyClass obj2 = new MyClass();
3
4// 线程1和线程2互斥(共享类锁)
5new Thread(() -> MyClass.staticMethod()).start();
6new Thread(() -> MyClass.staticMethod()).start();
7
8// 线程3和线程4也互斥(共享类锁)
9new Thread(() -> obj1.staticMethod()).start();
10new Thread(() -> obj2.staticMethod()).start();

3. 混合使用

java
1public class MyClass {
2 // 实例锁
3 public synchronized void instanceMethod() {
4 System.out.println("实例方法");
5 }
6
7 // 类锁
8 public static synchronized void staticMethod() {
9 System.out.println("静态方法");
10 }
11}
12
13// 不互斥(锁对象不同)
14new Thread(() -> new MyClass().instanceMethod()).start();
15new Thread(() -> MyClass.staticMethod()).start();

对比总结:

特性修饰实例方法修饰静态方法
锁对象this(实例对象)Class 对象
作用范围当前实例所有实例
互斥范围同一实例的同步方法所有静态同步方法
使用场景实例变量保护静态变量保护

最佳实践:

java
1public class Counter {
2 private int instanceCount = 0;
3 private static int staticCount = 0;
4
5 // 保护实例变量
6 public synchronized void incrementInstance() {
7 instanceCount++;
8 }
9
10 // 保护静态变量
11 public static synchronized void incrementStatic() {
12 staticCount++;
13 }
14
15 // 或者使用显式锁对象
16 private final Object instanceLock = new Object();
17 private static final Object staticLock = new Object();
18
19 public void increment() {
20 synchronized (instanceLock) {
21 instanceCount++;
22 }
23 }
24
25 public static void incrementStatic2() {
26 synchronized (staticLock) {
27 staticCount++;
28 }
29 }
30}

33. Java 中的 synchronized 轻量级锁是否会进行自旋?

答案:

是的,轻量级锁会进行自旋,但有条件限制。

自旋的原因:

  • 避免线程阻塞和唤醒的开销
  • 适用于锁持有时间短的场景
  • 线程在用户态自旋,不进入内核态

自旋过程:

java
1// 轻量级锁获取流程
2while (true) {
3 // 1. 尝试 CAS 获取锁
4 if (CAS成功) {
5 获取锁成功;
6 break;
7 }
8
9 // 2. CAS 失败,自旋重试
10 if (自旋次数 < 最大自旋次数) {
11 自旋次数++;
12 continue; // 继续自旋
13 } else {
14 // 3. 自旋失败,升级为重量级锁
15 升级为重量级锁();
16 阻塞等待();
17 break;
18 }
19}

自旋策略:

1. 固定次数自旋(JDK 1.6 之前)

java
1// 默认自旋10次
2-XX:PreBlockSpin=10

2. 自适应自旋(JDK 1.6+)

java
1// 根据历史自旋成功率动态调整
2if (上次自旋成功) {
3 允许更多次自旋;
4} else {
5 减少自旋次数;
6}
7
8// 如果某个锁自旋很少成功,可能直接省略自旋

自旋的条件:

  1. CPU 核心数 > 1

    • 单核 CPU 自旋无意义(持有锁的线程无法释放)
  2. 锁持有时间短

    • 自旋时间 < 线程阻塞/唤醒时间
  3. 竞争不激烈

    • 竞争激烈时直接升级为重量级锁

自旋优化示例:

java
1public class SpinLockDemo {
2 private AtomicReference<Thread> owner = new AtomicReference<>();
3
4 public void lock() {
5 Thread current = Thread.currentThread();
6
7 // 自旋获取锁
8 while (!owner.compareAndSet(null, current)) {
9 // 自旋等待
10 // CPU 空转,不释放 CPU
11 }
12 }
13
14 public void unlock() {
15 Thread current = Thread.currentThread();
16 owner.compareAndSet(current, null);
17 }
18}

自旋的优缺点:

优点:

  • 避免线程阻塞和上下文切换
  • 适合锁持有时间短的场景
  • 提高并发性能

缺点:

  • 占用 CPU 资源
  • 锁持有时间长时浪费 CPU
  • 可能导致 CPU 使用率高

JVM 参数控制:

bash
1# 开启/关闭自旋锁优化(默认开启)
2-XX:+UseSpinning
3-XX:-UseSpinning
4
5# 设置自旋次数(JDK 1.6 之前)
6-XX:PreBlockSpin=10
7
8# 开启自适应自旋(JDK 1.6+ 默认开启)
9-XX:+UseAdaptiveSpinning

锁升级与自旋的关系:

1偏向锁
2 ↓ 有竞争
3轻量级锁(自旋获取)
4 ↓ 自旋失败
5重量级锁(阻塞等待)

实际应用建议:

  • 锁持有时间短(几十个指令):自旋有效
  • 锁持有时间长:自旋浪费 CPU,应直接阻塞
  • 高并发场景:考虑使用 ReentrantLock 或无锁方案

34. Synchronized 能不能禁止指令重排序?

35. 当 Java 的 synchronized 升级到重量级锁后,所有线程都释放锁了,此时它还是重量级锁吗?

36. 什么是 Java 中的锁自适应自旋?

37. Synchronized 和 ReentrantLock 有什么区别?

38. Volatile 与 Synchronized 的区别是什么?

39. 如何优化 Java 中的锁的使用?

40. 你了解 Java 中的读写锁吗?

41. 什么是 Java 内存模型(JMM)?

42. 什么是 Java 中的原子性、可见性和有序性?

43. 什么是 Java 的 happens-before 规则?

44. 什么是 Java 中的指令重排?

45. Java 中的 final 关键字是否能保证变量的可见性?

46. 为什么在 Java 中需要使用 ThreadLocal?

47. Java 中的 ThreadLocal 是如何实现线程资源隔离的?

48. 为什么 Java 中的 ThreadLocal 对 key 的引用为弱引用?

49. Java 中使用 ThreadLocal 的最佳实践是什么?

50. Java 中的 InheritableThreadLocal 是什么?

51. ThreadLocal 的缺点?

52. 为什么 Netty 不使用 ThreadLocal 而是自定义了一个 FastThreadLocal ?

53. 什么是 Java 的 TransmittableThreadLocal?

54. Java 中 Thread.sleep 和 Thread.yield 的区别?

55. Java 中 Thread.sleep(0) 的作用是什么?

56. Java 中什么情况会导致死锁?如何避免?

57. Java 中 volatile 关键字的作用是什么?

58. 什么是 Java 中的 ABA 问题?

59. 在 Java 中主线程如何知晓创建的子线程是否执行成功?

60. 创建线程池有哪些方式?

61. 线程安全的集合有哪些?

62. Java 创建线程有哪些方式?


学习指南

核心要点:

  • Java 线程生命周期和状态转换
  • 锁机制和并发控制原理
  • 并发容器和原子操作
  • 内存模型和可见性保证

学习路径建议:

  1. 掌握 Java 线程基础和生命周期
  2. 深入理解锁机制和 AQS 原理
  3. 熟悉并发容器和原子类
  4. 掌握 JMM 和并发编程最佳实践

21. 什么是 Java 的 StampedLock?

答案:

StampedLock 是 Java 8 引入的一种锁,提供了三种模式的读写控制,性能优于 ReadWriteLock。

三种锁模式:

  1. 写锁(Writing):独占锁
  2. 悲观读锁(Reading):共享锁,不允许写
  3. 乐观读(Optimistic Reading):不加锁,通过版本号验证

基本使用:

java
1public class Point {
2 private double x, y;
3 private final StampedLock lock = new StampedLock();
4
5 // 写操作
6 public void move(double deltaX, double deltaY) {
7 long stamp = lock.writeLock(); // 获取写锁
8 try {
9 x += deltaX;
10 y += deltaY;
11 } finally {
12 lock.unlockWrite(stamp); // 释放写锁
13 }
14 }
15
16 // 乐观读
17 public double distanceFromOrigin() {
18 long stamp = lock.tryOptimisticRead(); // 乐观读
19 double currentX = x;
20 double currentY = y;
21
22 if (!lock.validate(stamp)) { // 验证是否有写操作
23 stamp = lock.readLock(); // 升级为悲观读锁
24 try {
25 currentX = x;
26 currentY = y;
27 } finally {
28 lock.unlockRead(stamp);
29 }
30 }
31
32 return Math.sqrt(currentX * currentX + currentY * currentY);
33 }
34
35 // 悲观读
36 public double getX() {
37 long stamp = lock.readLock();
38 try {
39 return x;
40 } finally {
41 lock.unlockRead(stamp);
42 }
43 }
44}

锁升级:

java
1// 读锁升级为写锁
2long stamp = lock.readLock();
3try {
4 // 读取数据
5 long writeStamp = lock.tryConvertToWriteLock(stamp);
6 if (writeStamp != 0L) {
7 stamp = writeStamp;
8 // 执行写操作
9 } else {
10 lock.unlockRead(stamp);
11 stamp = lock.writeLock();
12 // 执行写操作
13 }
14} finally {
15 lock.unlock(stamp);
16}

特点:

  • 乐观读不阻塞写操作,性能更好
  • 不支持重入
  • 不支持 Condition
  • 适合读多写少的场景

与 ReadWriteLock 对比:

特性StampedLockReadWriteLock
乐观读支持不支持
性能更高较低
重入不支持支持
Condition不支持支持

22. 什么是 Java 的 CompletableFuture?

答案:

CompletableFuture 是 Java 8 引入的异步编程工具,实现了 Future 和 CompletionStage 接口,支持链式调用和组合操作。

创建 CompletableFuture:

java
1// 1. 无返回值异步任务
2CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
3 System.out.println("异步任务");
4});
5
6// 2. 有返回值异步任务
7CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
8 return "结果";
9});
10
11// 3. 手动完成
12CompletableFuture<String> future3 = new CompletableFuture<>();
13future3.complete("手动设置结果");

链式调用:

java
1CompletableFuture.supplyAsync(() -> {
2 return "Hello";
3})
4.thenApply(s -> s + " World") // 转换结果
5.thenApply(String::toUpperCase) // 继续转换
6.thenAccept(System.out::println) // 消费结果
7.thenRun(() -> System.out.println("完成")); // 执行动作

组合操作:

java
1// 1. 两个任务都完成后执行
2CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
3CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
4
5CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> {
6 return s1 + " " + s2;
7});
8
9// 2. 任意一个完成后执行
10CompletableFuture<String> fastest = future1.applyToEither(future2, s -> s);
11
12// 3. 所有任务完成
13CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2);
14
15// 4. 任意任务完成
16CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2);

异常处理:

java
1CompletableFuture.supplyAsync(() -> {
2 if (true) throw new RuntimeException("错误");
3 return "结果";
4})
5.exceptionally(ex -> {
6 System.out.println("异常处理: " + ex.getMessage());
7 return "默认值";
8})
9.handle((result, ex) -> {
10 if (ex != null) {
11 return "异常: " + ex.getMessage();
12 }
13 return result;
14})
15.whenComplete((result, ex) -> {
16 if (ex != null) {
17 System.out.println("失败: " + ex.getMessage());
18 } else {
19 System.out.println("成功: " + result);
20 }
21});

实际应用场景:

java
1// 并行调用多个服务
2public CompletableFuture<OrderInfo> getOrderInfo(String orderId) {
3 CompletableFuture<Order> orderFuture =
4 CompletableFuture.supplyAsync(() -> orderService.getOrder(orderId));
5
6 CompletableFuture<User> userFuture =
7 CompletableFuture.supplyAsync(() -> userService.getUser(orderId));
8
9 CompletableFuture<Product> productFuture =
10 CompletableFuture.supplyAsync(() -> productService.getProduct(orderId));
11
12 return orderFuture.thenCombine(userFuture, (order, user) -> {
13 return new OrderInfo(order, user);
14 }).thenCombine(productFuture, (orderInfo, product) -> {
15 orderInfo.setProduct(product);
16 return orderInfo;
17 });
18}

常用方法总结:

  • thenApply:转换结果
  • thenAccept:消费结果
  • thenRun:执行动作
  • thenCompose:扁平化嵌套 Future
  • thenCombine:组合两个 Future
  • exceptionally:异常处理
  • handle:结果和异常统一处理
  • whenComplete:完成时回调

23. 什么是 Java 的 ForkJoinPool?

答案:

ForkJoinPool 是 Java 7 引入的线程池,专门用于执行可以递归分解的任务,采用"分而治之"的策略和工作窃取算法。

核心概念:

  1. Fork(分解):将大任务分解为小任务
  2. Join(合并):合并小任务的结果
  3. Work Stealing(工作窃取):空闲线程从其他线程的队列中窃取任务

使用方式:

1. RecursiveTask(有返回值)

java
1public class SumTask extends RecursiveTask<Long> {
2 private static final int THRESHOLD = 10000;
3 private long[] array;
4 private int start;
5 private int end;
6
7 public SumTask(long[] array, int start, int end) {
8 this.array = array;
9 this.start = start;
10 this.end = end;
11 }
12
13 @Override
14 protected Long compute() {
15 if (end - start <= THRESHOLD) {
16 // 任务足够小,直接计算
17 long sum = 0;
18 for (int i = start; i < end; i++) {
19 sum += array[i];
20 }
21 return sum;
22 } else {
23 // 任务太大,分解为两个子任务
24 int middle = (start + end) / 2;
25 SumTask leftTask = new SumTask(array, start, middle);
26 SumTask rightTask = new SumTask(array, middle, end);
27
28 // Fork:异步执行子任务
29 leftTask.fork();
30 rightTask.fork();
31
32 // Join:等待子任务完成并合并结果
33 long leftResult = leftTask.join();
34 long rightResult = rightTask.join();
35
36 return leftResult + rightResult;
37 }
38 }
39}
40
41// 使用
42ForkJoinPool pool = new ForkJoinPool();
43long[] array = new long[100000];
44SumTask task = new SumTask(array, 0, array.length);
45long result = pool.invoke(task);

2. RecursiveAction(无返回值)

java
1public class PrintTask extends RecursiveAction {
2 private static final int THRESHOLD = 50;
3 private int start;
4 private int end;
5
6 public PrintTask(int start, int end) {
7 this.start = start;
8 this.end = end;
9 }
10
11 @Override
12 protected void compute() {
13 if (end - start <= THRESHOLD) {
14 for (int i = start; i < end; i++) {
15 System.out.println(i);
16 }
17 } else {
18 int middle = (start + end) / 2;
19 PrintTask leftTask = new PrintTask(start, middle);
20 PrintTask rightTask = new PrintTask(middle, end);
21
22 invokeAll(leftTask, rightTask); // 执行所有子任务
23 }
24 }
25}

工作窃取算法:

1线程1队列: [Task1, Task2, Task3] ← 从尾部取任务
2线程2队列: [Task4, Task5] ← 从尾部取任务
3线程3队列: [] ← 空闲,从其他队列头部窃取任务
4
5线程3窃取 → 从线程1队列头部取 Task1

特点:

  • 适合计算密集型任务
  • 自动负载均衡(工作窃取)
  • 减少线程竞争(每个线程有自己的队列)
  • 默认线程数等于 CPU 核心数

应用场景:

  • 大数组排序、求和
  • 并行流(Stream.parallel())
  • 递归算法并行化
  • 图像处理、数据分析

与普通线程池对比:

特性ForkJoinPoolThreadPoolExecutor
任务类型可分解的递归任务独立任务
队列双端队列(每线程)单一队列
负载均衡工作窃取
适用场景计算密集型通用

24. 如何在 Java 中控制多个线程的执行顺序?

答案:

Java 提供了多种方式控制线程执行顺序:

1. 使用 join() 方法

java
1Thread t1 = new Thread(() -> System.out.println("线程1"));
2Thread t2 = new Thread(() -> System.out.println("线程2"));
3Thread t3 = new Thread(() -> System.out.println("线程3"));
4
5t1.start();
6t1.join(); // 等待 t1 完成
7
8t2.start();
9t2.join(); // 等待 t2 完成
10
11t3.start();
12t3.join(); // 等待 t3 完成

2. 使用 CountDownLatch

java
1CountDownLatch latch1 = new CountDownLatch(1);
2CountDownLatch latch2 = new CountDownLatch(1);
3
4new Thread(() -> {
5 System.out.println("线程1");
6 latch1.countDown();
7}).start();
8
9new Thread(() -> {
10 try {
11 latch1.await(); // 等待线程1完成
12 System.out.println("线程2");
13 latch2.countDown();
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17}).start();
18
19new Thread(() -> {
20 try {
21 latch2.await(); // 等待线程2完成
22 System.out.println("线程3");
23 } catch (InterruptedException e) {
24 e.printStackTrace();
25 }
26}).start();

3. 使用 CyclicBarrier

java
1CyclicBarrier barrier = new CyclicBarrier(3);
2
3// 所有线程同时开始
4for (int i = 0; i < 3; i++) {
5 final int id = i;
6 new Thread(() -> {
7 try {
8 System.out.println("线程" + id + " 准备");
9 barrier.await(); // 等待其他线程
10 System.out.println("线程" + id + " 开始执行");
11 } catch (Exception e) {
12 e.printStackTrace();
13 }
14 }).start();
15}

4. 使用 Semaphore

java
1Semaphore semaphore1 = new Semaphore(1);
2Semaphore semaphore2 = new Semaphore(0);
3Semaphore semaphore3 = new Semaphore(0);
4
5new Thread(() -> {
6 try {
7 semaphore1.acquire();
8 System.out.println("线程1");
9 semaphore2.release();
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13}).start();
14
15new Thread(() -> {
16 try {
17 semaphore2.acquire();
18 System.out.println("线程2");
19 semaphore3.release();
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23}).start();
24
25new Thread(() -> {
26 try {
27 semaphore3.acquire();
28 System.out.println("线程3");
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32}).start();

5. 使用 wait/notify

java
1public class SequentialExecution {
2 private int flag = 1;
3 private final Object lock = new Object();
4
5 public void thread1() {
6 synchronized (lock) {
7 while (flag != 1) {
8 try {
9 lock.wait();
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13 }
14 System.out.println("线程1");
15 flag = 2;
16 lock.notifyAll();
17 }
18 }
19
20 public void thread2() {
21 synchronized (lock) {
22 while (flag != 2) {
23 try {
24 lock.wait();
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }
28 }
29 System.out.println("线程2");
30 flag = 3;
31 lock.notifyAll();
32 }
33 }
34}

6. 使用 CompletableFuture

java
1CompletableFuture.runAsync(() -> {
2 System.out.println("线程1");
3})
4.thenRun(() -> {
5 System.out.println("线程2");
6})
7.thenRun(() -> {
8 System.out.println("线程3");
9});

7. 使用单线程线程池

java
1ExecutorService executor = Executors.newSingleThreadExecutor();
2
3executor.submit(() -> System.out.println("任务1"));
4executor.submit(() -> System.out.println("任务2"));
5executor.submit(() -> System.out.println("任务3"));
6
7executor.shutdown();

选择建议:

  • 简单顺序执行:join() 或单线程线程池
  • 复杂依赖关系:CountDownLatch 或 CompletableFuture
  • 循环执行:CyclicBarrier
  • 精确控制:Semaphore 或 wait/notify

25. 你使用过 Java 中的哪些阻塞队列?

答案:

Java 并发包提供了多种阻塞队列(BlockingQueue),用于生产者-消费者模式。

1. ArrayBlockingQueue(有界数组队列)

java
1// 创建容量为10的队列
2BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
3
4// 添加元素
5queue.put("element"); // 队列满时阻塞
6queue.offer("element", 1, TimeUnit.SECONDS); // 超时返回false
7
8// 获取元素
9String element = queue.take(); // 队列空时阻塞
10String element2 = queue.poll(1, TimeUnit.SECONDS); // 超时返回null

特点:

  • 基于数组实现
  • 有界队列,必须指定容量
  • FIFO 顺序
  • 支持公平/非公平锁

2. LinkedBlockingQueue(有界链表队列)

java
1// 无界队列(最大容量 Integer.MAX_VALUE)
2BlockingQueue<String> queue1 = new LinkedBlockingQueue<>();
3
4// 有界队列
5BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(100);

特点:

  • 基于链表实现
  • 可选有界/无界
  • FIFO 顺序
  • 吞吐量通常高于 ArrayBlockingQueue

3. PriorityBlockingQueue(优先级队列)

java
1BlockingQueue<Task> queue = new PriorityBlockingQueue<>();
2
3class Task implements Comparable<Task> {
4 private int priority;
5
6 @Override
7 public int compareTo(Task other) {
8 return Integer.compare(this.priority, other.priority);
9 }
10}

特点:

  • 基于堆实现
  • 无界队列
  • 按优先级排序
  • 不保证同优先级的顺序

4. DelayQueue(延迟队列)

java
1BlockingQueue<DelayedTask> queue = new DelayQueue<>();
2
3class DelayedTask implements Delayed {
4 private long delayTime;
5 private long expire;
6
7 @Override
8 public long getDelay(TimeUnit unit) {
9 return unit.convert(expire - System.currentTimeMillis(),
10 TimeUnit.MILLISECONDS);
11 }
12
13 @Override
14 public int compareTo(Delayed o) {
15 return Long.compare(this.expire, ((DelayedTask) o).expire);
16 }
17}

特点:

  • 元素必须实现 Delayed 接口
  • 只有到期的元素才能被取出
  • 适用于定时任务、缓存过期

5. SynchronousQueue(同步队列)

java
1BlockingQueue<String> queue = new SynchronousQueue<>();
2
3// 生产者
4new Thread(() -> {
5 try {
6 queue.put("data"); // 阻塞直到有消费者取走
7 } catch (InterruptedException e) {
8 e.printStackTrace();
9 }
10}).start();
11
12// 消费者
13new Thread(() -> {
14 try {
15 String data = queue.take(); // 阻塞直到有生产者放入
16 } catch (InterruptedException e) {
17 e.printStackTrace();
18 }
19}).start();

特点:

  • 不存储元素
  • 每个 put 必须等待一个 take
  • 适用于传递性场景
  • CachedThreadPool 使用此队列

6. LinkedTransferQueue(传输队列)

java
1TransferQueue<String> queue = new LinkedTransferQueue<>();
2
3// 生产者
4queue.transfer("data"); // 阻塞直到消费者接收
5
6// 消费者
7String data = queue.take();

特点:

  • 无界队列
  • 支持 transfer 方法(直接传递)
  • 性能优于 LinkedBlockingQueue

7. LinkedBlockingDeque(双端队列)

java
1BlockingDeque<String> deque = new LinkedBlockingDeque<>();
2
3// 可以从两端操作
4deque.putFirst("first");
5deque.putLast("last");
6String first = deque.takeFirst();
7String last = deque.takeLast();

特点:

  • 双端队列
  • 可以从头尾两端操作
  • 适用于工作窃取模式

对比总结:

队列类型有界性数据结构特点
ArrayBlockingQueue有界数组固定容量
LinkedBlockingQueue可选链表高吞吐量
PriorityBlockingQueue无界优先级排序
DelayQueue无界延迟获取
SynchronousQueue0直接传递
LinkedTransferQueue无界链表支持传输
LinkedBlockingDeque可选链表双端操作

使用场景:

  • 固定容量限流:ArrayBlockingQueue
  • 生产者-消费者:LinkedBlockingQueue
  • 任务优先级:PriorityBlockingQueue
  • 定时任务:DelayQueue
  • 线程池任务传递:SynchronousQueue

26. 你使用过 Java 中的哪些原子类?

答案:

Java 提供了 java.util.concurrent.atomic 包,包含多种原子类,保证操作的原子性。

1. 基本类型原子类

java
1// AtomicInteger
2AtomicInteger count = new AtomicInteger(0);
3count.incrementAndGet(); // i++
4count.getAndIncrement(); // ++i
5count.addAndGet(5); // i += 5
6count.compareAndSet(0, 1); // CAS操作
7
8// AtomicLong
9AtomicLong longValue = new AtomicLong(0L);
10
11// AtomicBoolean
12AtomicBoolean flag = new AtomicBoolean(false);
13flag.compareAndSet(false, true);

2. 数组类型原子类

java
1// AtomicIntegerArray
2AtomicIntegerArray array = new AtomicIntegerArray(10);
3array.incrementAndGet(0); // 数组索引0的元素+1
4array.compareAndSet(0, 1, 2); // CAS操作
5
6// AtomicLongArray
7AtomicLongArray longArray = new AtomicLongArray(10);
8
9// AtomicReferenceArray
10AtomicReferenceArray<String> refArray = new AtomicReferenceArray<>(10);

3. 引用类型原子类

java
1// AtomicReference
2AtomicReference<User> userRef = new AtomicReference<>();
3User oldUser = new User("张三");
4User newUser = new User("李四");
5userRef.compareAndSet(oldUser, newUser);
6
7// AtomicStampedReference(解决ABA问题)
8AtomicStampedReference<String> stampedRef =
9 new AtomicStampedReference<>("初始值", 0);
10int stamp = stampedRef.getStamp();
11stampedRef.compareAndSet("初始值", "新值", stamp, stamp + 1);
12
13// AtomicMarkableReference(标记是否被修改过)
14AtomicMarkableReference<String> markableRef =
15 new AtomicMarkableReference<>("初始值", false);
16markableRef.compareAndSet("初始值", "新值", false, true);

4. 字段更新器

java
1class User {
2 volatile int age;
3 volatile String name;
4}
5
6// AtomicIntegerFieldUpdater
7AtomicIntegerFieldUpdater<User> ageUpdater =
8 AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
9User user = new User();
10ageUpdater.incrementAndGet(user);
11
12// AtomicReferenceFieldUpdater
13AtomicReferenceFieldUpdater<User, String> nameUpdater =
14 AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
15nameUpdater.compareAndSet(user, "旧名字", "新名字");

应用场景示例:

java
1// 1. 计数器
2public class Counter {
3 private AtomicInteger count = new AtomicInteger(0);
4
5 public void increment() {
6 count.incrementAndGet();
7 }
8
9 public int getCount() {
10 return count.get();
11 }
12}
13
14// 2. 单例模式
15public class Singleton {
16 private static final AtomicReference<Singleton> INSTANCE =
17 new AtomicReference<>();
18
19 public static Singleton getInstance() {
20 while (true) {
21 Singleton current = INSTANCE.get();
22 if (current != null) {
23 return current;
24 }
25 current = new Singleton();
26 if (INSTANCE.compareAndSet(null, current)) {
27 return current;
28 }
29 }
30 }
31}

27. 你使用过 Java 的累加器吗?

答案:

Java 8 引入了累加器(Accumulator),在高并发场景下性能优于原子类。

1. LongAdder(长整型累加器)

java
1LongAdder adder = new LongAdder();
2
3// 多线程累加
4for (int i = 0; i < 10; i++) {
5 new Thread(() -> {
6 for (int j = 0; j < 1000; j++) {
7 adder.increment(); // 加1
8 adder.add(5); // 加5
9 }
10 }).start();
11}
12
13// 获取结果
14long sum = adder.sum();

2. LongAccumulator(长整型累积器)

java
1// 自定义累积函数(求最大值)
2LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
3
4accumulator.accumulate(100);
5accumulator.accumulate(200);
6accumulator.accumulate(50);
7
8long max = accumulator.get(); // 200

3. DoubleAdder 和 DoubleAccumulator

java
1DoubleAdder doubleAdder = new DoubleAdder();
2doubleAdder.add(1.5);
3double sum = doubleAdder.sum();
4
5DoubleAccumulator doubleAccumulator =
6 new DoubleAccumulator(Double::sum, 0.0);

性能对比:

java
1// AtomicLong vs LongAdder 性能测试
2public class PerformanceTest {
3 public static void testAtomicLong() {
4 AtomicLong atomicLong = new AtomicLong(0);
5 // 10个线程,每个累加100万次
6 // AtomicLong: 约2000ms
7 }
8
9 public static void testLongAdder() {
10 LongAdder longAdder = new LongAdder();
11 // 10个线程,每个累加100万次
12 // LongAdder: 约500ms(快4倍)
13 }
14}

原理:

  • AtomicLong:所有线程竞争同一个变量,CAS 冲突多
  • LongAdder:每个线程维护自己的计数器(Cell),最后汇总,减少竞争

内部结构:

1LongAdder
2├── base (基础值)
3└── cells[] (Cell数组)
4 ├── Cell[0] (线程1的计数)
5 ├── Cell[1] (线程2的计数)
6 └── Cell[2] (线程3的计数)
7
8sum() = base + Cell[0] + Cell[1] + Cell[2] + ...

选择建议:

  • 低并发场景:AtomicLong(更简单)
  • 高并发累加:LongAdder(性能更好)
  • 需要 CAS 操作:AtomicLong
  • 只需要累加:LongAdder

28. 什么是 Java 的 CAS(Compare-And-Swap)操作?

答案:

CAS(Compare-And-Swap)是一种无锁的原子操作,用于实现并发控制。

原理: CAS 包含三个操作数:

  • V:内存位置(变量)
  • E:预期值(Expected)
  • N:新值(New)

只有当 V 的值等于 E 时,才将 V 的值设置为 N,否则不做任何操作。整个过程是原子的。

伪代码:

java
1boolean compareAndSwap(Variable V, int E, int N) {
2 if (V == E) {
3 V = N;
4 return true;
5 }
6 return false;
7}

Java 中的 CAS:

java
1AtomicInteger count = new AtomicInteger(0);
2
3// CAS 操作
4boolean success = count.compareAndSet(0, 1);
5// 如果当前值是0,则设置为1,返回true
6// 如果当前值不是0,不做任何操作,返回false
7
8// 自旋 CAS
9public void increment() {
10 int oldValue;
11 int newValue;
12 do {
13 oldValue = count.get();
14 newValue = oldValue + 1;
15 } while (!count.compareAndSet(oldValue, newValue));
16}

底层实现:

java
1// Unsafe 类提供的 CAS 方法
2public final native boolean compareAndSwapInt(
3 Object o, // 对象
4 long offset, // 字段偏移量
5 int expected, // 预期值
6 int x // 新值
7);
8
9// CPU 指令(x86)
10lock cmpxchg [内存地址], 新值

CAS 的优点:

  1. 无锁,避免线程阻塞和上下文切换
  2. 性能高于 synchronized
  3. 适合低并发场景

CAS 的缺点:

1. ABA 问题

java
1// 线程1:读取值A
2int value = atomicInt.get(); // A
3
4// 线程2:A -> B -> A
5atomicInt.compareAndSet(A, B);
6atomicInt.compareAndSet(B, A);
7
8// 线程1:CAS成功,但值已经被修改过
9atomicInt.compareAndSet(A, C); // 成功,但不知道中间变化
10
11// 解决方案:使用版本号
12AtomicStampedReference<Integer> stampedRef =
13 new AtomicStampedReference<>(A, 0);
14int stamp = stampedRef.getStamp();
15stampedRef.compareAndSet(A, C, stamp, stamp + 1);

2. 循环时间长开销大

java
1// 高并发下,CAS 失败次数多,CPU 开销大
2public void increment() {
3 int oldValue;
4 int newValue;
5 do {
6 oldValue = count.get();
7 newValue = oldValue + 1;
8 // 高并发下可能循环很多次
9 } while (!count.compareAndSet(oldValue, newValue));
10}
11
12// 解决方案:使用 LongAdder 或加锁

3. 只能保证一个共享变量的原子操作

java
1// 不能同时 CAS 多个变量
2// 解决方案:
3// 1. 使用 AtomicReference 包装多个变量
4class Pair {
5 int a;
6 int b;
7}
8AtomicReference<Pair> pairRef = new AtomicReference<>(new Pair());
9
10// 2. 使用锁
11synchronized (lock) {
12 a++;
13 b++;
14}

应用场景:

  • 原子类(AtomicInteger、AtomicReference)
  • 并发容器(ConcurrentHashMap)
  • AQS(AbstractQueuedSynchronizer)
  • 乐观锁实现

29. 说说 AQS 吧?

答案:

AQS(AbstractQueuedSynchronizer)是 Java 并发包的核心基础框架,用于构建锁和同步器。

核心思想:

  • 使用一个 int 类型的 state 变量表示同步状态
  • 使用 FIFO 队列管理等待线程
  • 提供独占模式和共享模式

核心组件:

1. 同步状态(state)

java
1private volatile int state;
2
3// 获取状态
4protected final int getState()
5
6// 设置状态
7protected final void setState(int newState)
8
9// CAS 设置状态
10protected final boolean compareAndSetState(int expect, int update)

2. 等待队列(CLH 队列)

1head -> Node1 -> Node2 -> Node3 -> tail
2 (等待) (等待) (等待)

每个 Node 包含:

  • thread:等待的线程
  • waitStatus:等待状态
  • prev/next:前驱/后继节点

工作流程:

独占模式(如 ReentrantLock):

java
1// 1. 尝试获取锁
2public final void acquire(int arg) {
3 if (!tryAcquire(arg) && // 尝试获取(子类实现)
4 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { // 加入队列等待
5 selfInterrupt();
6 }
7}
8
9// 2. 释放锁
10public final boolean release(int arg) {
11 if (tryRelease(arg)) { // 尝试释放(子类实现)
12 Node h = head;
13 if (h != null && h.waitStatus != 0)
14 unparkSuccessor(h); // 唤醒后继节点
15 return true;
16 }
17 return false;
18}

共享模式(如 Semaphore、CountDownLatch):

java
1// 获取共享锁
2public final void acquireShared(int arg) {
3 if (tryAcquireShared(arg) < 0) // 尝试获取
4 doAcquireShared(arg); // 加入队列等待
5}
6
7// 释放共享锁
8public final boolean releaseShared(int arg) {
9 if (tryReleaseShared(arg)) { // 尝试释放
10 doReleaseShared(); // 唤醒后继节点
11 return true;
12 }
13 return false;
14}

自定义同步器示例:

java
1// 自定义互斥锁
2public class Mutex implements Lock {
3 private static class Sync extends AbstractQueuedSynchronizer {
4 // 尝试获取锁
5 @Override
6 protected boolean tryAcquire(int arg) {
7 return compareAndSetState(0, 1);
8 }
9
10 // 尝试释放锁
11 @Override
12 protected boolean tryRelease(int arg) {
13 setState(0);
14 return true;
15 }
16
17 // 是否被独占
18 @Override
19 protected boolean isHeldExclusively() {
20 return getState() == 1;
21 }
22
23 Condition newCondition() {
24 return new ConditionObject();
25 }
26 }
27
28 private final Sync sync = new Sync();
29
30 @Override
31 public void lock() {
32 sync.acquire(1);
33 }
34
35 @Override
36 public void unlock() {
37 sync.release(1);
38 }
39
40 // 其他方法实现...
41}

基于 AQS 的同步器:

  • ReentrantLock:独占锁
  • ReentrantReadWriteLock:读写锁
  • Semaphore:信号量
  • CountDownLatch:倒计数门栓
  • CyclicBarrier:循环栅栏(基于 ReentrantLock)

AQS 的优势:

  1. 提供统一的框架,简化同步器实现
  2. 高效的等待队列管理
  3. 支持可中断、超时、公平/非公平
  4. 提供 Condition 支持

30. Java 中 ReentrantLock 的实现原理是什么?

答案:

ReentrantLock 是基于 AQS 实现的可重入独占锁。

核心特性:

  1. 可重入:同一线程可以多次获取锁
  2. 公平/非公平:支持两种模式
  3. 可中断:支持中断等待
  4. 可超时:支持超时获取锁
  5. Condition 支持:支持多个条件变量

重入实现:

java
1protected final boolean tryAcquire(int acquires) {
2 final Thread current = Thread.currentThread();
3 int c = getState();
4
5 if (c == 0) {
6 // 锁空闲,尝试获取
7 if (compareAndSetState(0, acquires)) {
8 setExclusiveOwnerThread(current);
9 return true;
10 }
11 } else if (current == getExclusiveOwnerThread()) {
12 // 当前线程已持有锁,重入
13 int nextc = c + acquires;
14 if (nextc < 0) // 溢出检查
15 throw new Error("Maximum lock count exceeded");
16 setState(nextc); // 增加重入次数
17 return true;
18 }
19 return false;
20}

解锁流程:

java
1protected final boolean tryRelease(int releases) {
2 int c = getState() - releases;
3 if (Thread.currentThread() != getExclusiveOwnerThread())
4 throw new IllegalMonitorStateException();
5
6 boolean free = false;
7 if (c == 0) {
8 // 重入次数归零,完全释放锁
9 free = true;
10 setExclusiveOwnerThread(null);
11 }
12 setState(c); // 减少重入次数
13 return free;
14}

使用示例:

java
1ReentrantLock lock = new ReentrantLock();
2
3// 基本使用
4lock.lock();
5try {
6 // 临界区代码
7} finally {
8 lock.unlock();
9}
10
11// 可中断
12try {
13 lock.lockInterruptibly();
14 // 临界区代码
15} catch (InterruptedException e) {
16 // 处理中断
17} finally {
18 lock.unlock();
19}
20
21// 超时获取
22if (lock.tryLock(1, TimeUnit.SECONDS)) {
23 try {
24 // 临界区代码
25 } finally {
26 lock.unlock();
27 }
28} else {
29 // 获取锁失败
30}

公平锁 vs 非公平锁:

特性公平锁非公平锁
获取顺序按排队顺序可插队
性能较低较高
吞吐量较低较高
线程切换更多更少
适用场景需要公平性追求性能

31. Java 的 synchronized 是怎么实现的?

答案:

synchronized 是 Java 提供的内置锁机制,基于对象监视器(Monitor)实现。

实现层次:

1. 字节码层面

java
1public void syncMethod() {
2 synchronized (this) {
3 // 临界区代码
4 }
5}
6
7// 字节码
8monitorenter // 进入监视器(获取锁)
9// 临界区代码
10monitorexit // 退出监视器(释放锁)
11monitorexit // 异常情况的释放

2. 对象头(Mark Word)

Java 对象在内存中的布局:

1|--------------------------------------------------------------|
2| Object Header (对象头) |
3|--------------------------------------------------------------|
4| Mark Word (标记字段,8字节) |
5| Class Pointer (类型指针,4/8字节) |
6|--------------------------------------------------------------|
7| Instance Data (实例数据) |
8|--------------------------------------------------------------|
9| Padding (对齐填充) |
10|--------------------------------------------------------------|

Mark Word 存储锁信息:

1|----------|---------------------------|
2| 锁状态 | Mark Word 内容 |
3|----------|---------------------------|
4| 无锁 | hashCode | age | 0 | 01 |
5| 偏向锁 | threadID | epoch | 1 | 01 |
6| 轻量级锁 | 栈中锁记录指针 | 00 |
7| 重量级锁 | Monitor 指针 | 10 |
8| GC标记 | 空 | 11 |
9|----------|---------------------------|

3. 锁升级过程

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

偏向锁(Biased Locking):

  • 第一次获取锁时,CAS 将线程 ID 写入 Mark Word
  • 再次获取锁(同一线程),直接执行(无需 CAS)
  • 其他线程竞争时,撤销偏向锁,升级为轻量级锁

轻量级锁(Lightweight Locking):

  • 在栈帧中创建锁记录(Lock Record)
  • CAS 将对象头的 Mark Word 替换为指向锁记录的指针
  • 成功则获取轻量级锁
  • 失败则自旋重试
  • 自旋一定次数后升级为重量级锁

重量级锁(Heavyweight Locking):

  • 对象头指向 Monitor 对象
  • 线程进入 Monitor 的 EntryList
  • 竞争 Monitor 的 owner
  • 获取失败则阻塞(park)

Monitor 结构:

java
1ObjectMonitor {
2 _owner; // 持有锁的线程
3 _EntryList; // 等待获取锁的线程队列
4 _WaitSet; // 调用 wait() 的线程队列
5 _count; // 重入次数
6}

完整流程图:

1对象创建
2
3无锁状态(001)
4 ↓ 线程1访问
5偏向锁(101)- 线程ID = 线程1
6 ↓ 线程2竞争
7轻量级锁(00)- 自旋获取
8 ↓ 自旋失败/竞争激烈
9重量级锁(10)- 阻塞等待

优化技术:

  1. 锁消除:JIT 编译器检测到不可能存在竞争,消除锁
  2. 锁粗化:多个连续的加锁解锁操作合并为一次
  3. 自适应自旋:根据历史自旋成功率动态调整自旋次数

32. Synchronized 修饰静态方法和修饰普通方法有什么区别?

答案:

主要区别在于锁的对象不同:

1. 修饰普通方法(实例方法)

java
1public class MyClass {
2 // 锁对象是 this(当前实例)
3 public synchronized void instanceMethod() {
4 // 临界区
5 }
6
7 // 等价于
8 public void instanceMethod2() {
9 synchronized (this) {
10 // 临界区
11 }
12 }
13}

特点:

  • 锁对象是当前实例(this)
  • 不同实例之间不互斥
  • 同一实例的多个同步方法互斥

2. 修饰静态方法(类方法)

java
1public class MyClass {
2 // 锁对象是 MyClass.class(类对象)
3 public static synchronized void staticMethod() {
4 // 临界区
5 }
6
7 // 等价于
8 public static void staticMethod2() {
9 synchronized (MyClass.class) {
10 // 临界区
11 }
12 }
13}

特点:

  • 锁对象是 Class 对象(MyClass.class)
  • 所有实例共享同一个锁
  • 类的所有静态同步方法互斥

对比总结:

特性修饰实例方法修饰静态方法
锁对象this(实例对象)Class 对象
作用范围当前实例所有实例
互斥范围同一实例的同步方法所有静态同步方法
使用场景实例变量保护静态变量保护

33. Java 中的 synchronized 轻量级锁是否会进行自旋?

答案:

是的,轻量级锁会进行自旋,但有条件限制。

自旋的原因:

  • 避免线程阻塞和唤醒的开销
  • 适用于锁持有时间短的场景
  • 线程在用户态自旋,不进入内核态

自旋过程:

java
1// 轻量级锁获取流程
2while (true) {
3 // 1. 尝试 CAS 获取锁
4 if (CAS成功) {
5 获取锁成功;
6 break;
7 }
8
9 // 2. CAS 失败,自旋重试
10 if (自旋次数 < 最大自旋次数) {
11 自旋次数++;
12 continue; // 继续自旋
13 } else {
14 // 3. 自旋失败,升级为重量级锁
15 升级为重量级锁();
16 阻塞等待();
17 break;
18 }
19}

自旋策略:

1. 固定次数自旋(JDK 1.6 之前)

java
1// 默认自旋10次
2-XX:PreBlockSpin=10

2. 自适应自旋(JDK 1.6+)

  • 根据历史自旋成功率动态调整
  • 如果上次自旋成功,允许更多次自旋
  • 如果某个锁自旋很少成功,可能直接省略自旋

自旋的条件:

  1. CPU 核心数 > 1(单核 CPU 自旋无意义)
  2. 锁持有时间短
  3. 竞争不激烈

JVM 参数控制:

bash
1# 开启/关闭自旋锁优化(默认开启)
2-XX:+UseSpinning
3-XX:-UseSpinning
4
5# 设置自旋次数(JDK 1.6 之前)
6-XX:PreBlockSpin=10
7
8# 开启自适应自旋(JDK 1.6+ 默认开启)
9-XX:+UseAdaptiveSpinning

34. Synchronized 能不能禁止指令重排序?

答案:

能。synchronized 可以禁止指令重排序,保证有序性。

原理:

synchronized 通过以下机制保证有序性:

  1. happens-before 规则

    • 对一个锁的解锁 happens-before 于后续对这个锁的加锁
    • 保证解锁前的所有操作对加锁后的操作可见
  2. 内存屏障

    • 进入 synchronized 块时插入 Load 屏障
    • 退出 synchronized 块时插入 Store 屏障
    • 防止临界区内的指令重排到临界区外

示例:

java
1public class SynchronizedOrdering {
2 private int a = 0;
3 private int b = 0;
4
5 public synchronized void method1() {
6 a = 1; // 操作1
7 b = 2; // 操作2
8 }
9
10 public synchronized void method2() {
11 int x = b; // 操作3
12 int y = a; // 操作4
13 }
14}

保证:

  • 操作1和操作2不会重排到 synchronized 块外
  • 操作3和操作4不会重排到 synchronized 块外
  • 如果线程1先执行 method1,线程2后执行 method2,则线程2一定能看到 a=1, b=2

与 volatile 的区别:

特性synchronizedvolatile
原子性保证不保证
可见性保证保证
有序性保证保证
性能较低较高
适用场景复合操作单个变量

35. 当 Java 的 synchronized 升级到重量级锁后,所有线程都释放锁了,此时它还是重量级锁吗?

答案:

是的,锁升级是单向的,不会降级(在大多数 JVM 实现中)。

原因:

  1. 性能考虑

    • 锁降级需要额外的检测和处理逻辑
    • 降级过程本身有开销
    • 如果频繁升级降级,反而降低性能
  2. 实现复杂度

    • 需要判断何时降级
    • 需要处理降级过程中的并发问题
    • 增加 JVM 实现复杂度
  3. 实际场景

    • 一旦升级为重量级锁,说明存在竞争
    • 竞争可能再次发生
    • 保持重量级锁状态更合理

锁升级路径(单向):

1无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2 ↓ ↓ ↓
3 不可逆 不可逆 不可逆

特殊情况:

某些 JVM 实现(如 JDK 15+ 的 ZGC)在 GC 时可能会重置锁状态,但这不是常规的锁降级机制。

JVM 参数:

bash
1# 禁用偏向锁(直接从无锁到轻量级锁)
2-XX:-UseBiasedLocking
3
4# 偏向锁延迟启动时间(默认4秒)
5-XX:BiasedLockingStartupDelay=0

最佳实践:

  • 避免不必要的锁竞争
  • 减少锁持有时间
  • 使用合适的并发工具类
  • 考虑使用 ReentrantLock(可以更灵活地控制)

36. 什么是 Java 中的锁自适应自旋?

答案:

锁自适应自旋是 JDK 1.6 引入的优化技术,根据历史自旋成功率动态调整自旋次数。

传统自旋(固定次数):

java
1// JDK 1.6 之前,固定自旋10次
2for (int i = 0; i < 10; i++) {
3 if (tryAcquireLock()) {
4 return; // 获取成功
5 }
6}
7// 自旋失败,阻塞

自适应自旋:

java
1// 根据历史记录动态调整
2if (上次在这个锁上自旋成功) {
3 允许更长时间的自旋;
4} else if (上次自旋失败) {
5 减少自旋时间或直接阻塞;
6}
7
8// 如果某个锁很少自旋成功,可能直接跳过自旋
9if (这个锁历史上自旋成功率很低) {
10 直接阻塞,不自旋;
11}

自适应策略:

  1. 基于锁的历史

    • 记录每个锁的自旋成功率
    • 成功率高的锁允许更多自旋
    • 成功率低的锁减少或跳过自旋
  2. 基于线程的历史

    • 如果线程最近自旋成功过,允许更多自旋
    • 如果线程最近自旋总是失败,减少自旋
  3. 基于持有锁的线程状态

    • 如果持有锁的线程正在运行,自旋等待
    • 如果持有锁的线程已被挂起,直接阻塞

优点:

  • 更智能的自旋策略
  • 减少无效自旋,降低 CPU 浪费
  • 提高锁的整体性能

JVM 参数:

bash
1# 开启自适应自旋(JDK 1.6+ 默认开启)
2-XX:+UseAdaptiveSpinning
3
4# 关闭自适应自旋
5-XX:-UseAdaptiveSpinning

示例场景:

java
1// 场景1:锁持有时间短,竞争少
2// 自适应自旋会增加自旋次数,提高性能
3
4// 场景2:锁持有时间长,竞争激烈
5// 自适应自旋会减少自旋次数,避免 CPU 浪费
6
7// 场景3:某个锁从不自旋成功
8// 自适应自旋会跳过自旋,直接阻塞

37. Synchronized 和 ReentrantLock 有什么区别?

答案:

对比总结:

特性synchronizedReentrantLock
实现层面JVM 内置(关键字)JDK 实现(类)
锁的获取/释放自动手动(需要 try-finally)
可中断不支持支持(lockInterruptibly)
超时获取不支持支持(tryLock(timeout))
公平锁非公平支持公平/非公平
条件变量单个(wait/notify)多个(Condition)
可重入支持支持
锁状态查询不支持支持(isLocked、getHoldCount)
性能JDK 1.6+ 优化后相当相当

1. 使用方式

java
1// synchronized:自动释放
2public synchronized void method() {
3 // 临界区
4}
5
6// ReentrantLock:手动释放
7ReentrantLock lock = new ReentrantLock();
8lock.lock();
9try {
10 // 临界区
11} finally {
12 lock.unlock(); // 必须在 finally 中释放
13}

2. 可中断

java
1// synchronized:不可中断
2synchronized (lock) {
3 // 无法响应中断
4}
5
6// ReentrantLock:可中断
7try {
8 lock.lockInterruptibly();
9 // 可以响应中断
10} catch (InterruptedException e) {
11 // 处理中断
12}

3. 超时获取

java
1// synchronized:不支持超时
2
3// ReentrantLock:支持超时
4if (lock.tryLock(1, TimeUnit.SECONDS)) {
5 try {
6 // 获取锁成功
7 } finally {
8 lock.unlock();
9 }
10} else {
11 // 获取锁超时
12}

4. 公平锁

java
1// synchronized:只支持非公平锁
2
3// ReentrantLock:支持公平锁
4ReentrantLock fairLock = new ReentrantLock(true);

5. 条件变量

java
1// synchronized:单个条件变量
2synchronized (lock) {
3 lock.wait();
4 lock.notify();
5}
6
7// ReentrantLock:多个条件变量
8Condition condition1 = lock.newCondition();
9Condition condition2 = lock.newCondition();
10condition1.await();
11condition1.signal();

6. 锁状态查询

java
1// synchronized:不支持
2
3// ReentrantLock:支持
4boolean isLocked = lock.isLocked();
5int holdCount = lock.getHoldCount();
6boolean hasQueuedThreads = lock.hasQueuedThreads();

选择建议:

  • 简单场景:使用 synchronized(代码简洁)
  • 需要高级功能:使用 ReentrantLock
    • 可中断
    • 超时获取
    • 公平锁
    • 多个条件变量
    • 锁状态查询

38. Volatile 与 Synchronized 的区别是什么?

答案:

对比总结:

特性volatilesynchronized
类型关键字(变量修饰符)关键字(方法/代码块)
原子性不保证保证
可见性保证保证
有序性保证(禁止重排)保证
阻塞不阻塞可能阻塞
性能较低
适用场景单个变量复合操作

1. 原子性

java
1// volatile:不保证原子性
2private volatile int count = 0;
3
4public void increment() {
5 count++; // 非原子操作,线程不安全
6}
7
8// synchronized:保证原子性
9private int count = 0;
10
11public synchronized void increment() {
12 count++; // 原子操作,线程安全
13}

2. 可见性

java
1// volatile:保证可见性
2private volatile boolean flag = false;
3
4// 线程1
5flag = true; // 立即对其他线程可见
6
7// 线程2
8while (!flag) {
9 // 能立即看到 flag 的变化
10}
11
12// synchronized:也保证可见性
13private boolean flag = false;
14
15synchronized (lock) {
16 flag = true; // 解锁时刷新到主内存
17}
18
19synchronized (lock) {
20 if (flag) { // 加锁时从主内存读取
21 // ...
22 }
23}

3. 有序性

java
1// volatile:禁止指令重排
2private volatile boolean initialized = false;
3private int value;
4
5// 线程1
6value = 10;
7initialized = true; // 不会重排到 value = 10 之前
8
9// 线程2
10if (initialized) {
11 int x = value; // 一定能看到 value = 10
12}
13
14// synchronized:也禁止指令重排
15synchronized (lock) {
16 value = 10;
17 initialized = true;
18}

4. 使用场景

volatile 适用场景:

java
1// 1. 状态标志
2private volatile boolean running = true;
3
4public void stop() {
5 running = false;
6}
7
8public void run() {
9 while (running) {
10 // 执行任务
11 }
12}
13
14// 2. 双重检查锁定(DCL)
15private volatile static Singleton instance;
16
17public static Singleton getInstance() {
18 if (instance == null) {
19 synchronized (Singleton.class) {
20 if (instance == null) {
21 instance = new Singleton();
22 }
23 }
24 }
25 return instance;
26}
27
28// 3. 独立观察
29private volatile long lastUpdateTime;
30
31public void update() {
32 // 更新数据
33 lastUpdateTime = System.currentTimeMillis();
34}

synchronized 适用场景:

java
1// 1. 复合操作
2private int count = 0;
3
4public synchronized void increment() {
5 count++; // 读-改-写
6}
7
8// 2. 多个变量的一致性
9private int a = 0;
10private int b = 0;
11
12public synchronized void update() {
13 a++;
14 b++; // 保证 a 和 b 同时更新
15}
16
17// 3. 等待/通知机制
18synchronized (lock) {
19 while (!condition) {
20 lock.wait();
21 }
22 // 执行操作
23 lock.notify();
24}

性能对比:

  • volatile:读写操作几乎无开销
  • synchronized:有锁竞争时开销较大

选择建议:

  • 单个变量的可见性:volatile
  • 复合操作:synchronized 或 Lock
  • 需要等待/通知:synchronized 或 Lock + Condition

39. 如何优化 Java 中的锁的使用?

答案:

1. 减少锁的持有时间

java
1// 不好的做法
2public synchronized void method() {
3 // 大量非临界区代码
4 doSomething();
5 // 临界区代码
6 criticalSection();
7 // 更多非临界区代码
8 doMore();
9}
10
11// 优化后
12public void method() {
13 doSomething();
14
15 synchronized (lock) {
16 // 只锁临界区
17 criticalSection();
18 }
19
20 doMore();
21}

2. 减小锁的粒度

java
1// 不好的做法:锁整个对象
2public class UserService {
3 private synchronized void updateUser() { }
4 private synchronized void updateOrder() { }
5}
6
7// 优化后:使用不同的锁
8public class UserService {
9 private final Object userLock = new Object();
10 private final Object orderLock = new Object();
11
12 public void updateUser() {
13 synchronized (userLock) { }
14 }
15
16 public void updateOrder() {
17 synchronized (orderLock) { }
18 }
19}

3. 锁分段(Lock Striping)

java
1// ConcurrentHashMap 的实现思想
2public class StripedMap {
3 private static final int N_LOCKS = 16;
4 private final Node[] buckets;
5 private final Object[] locks;
6
7 public StripedMap(int numBuckets) {
8 buckets = new Node[numBuckets];
9 locks = new Object[N_LOCKS];
10 for (int i = 0; i < N_LOCKS; i++) {
11 locks[i] = new Object();
12 }
13 }
14
15 private final int hash(Object key) {
16 return Math.abs(key.hashCode() % buckets.length);
17 }
18
19 public Object get(Object key) {
20 int hash = hash(key);
21 synchronized (locks[hash % N_LOCKS]) {
22 // 访问 buckets[hash]
23 }
24 }
25}

4. 使用读写锁

java
1// 不好的做法:读写都用同一个锁
2private final Object lock = new Object();
3
4public Object read() {
5 synchronized (lock) {
6 return data;
7 }
8}
9
10public void write(Object newData) {
11 synchronized (lock) {
12 data = newData;
13 }
14}
15
16// 优化后:使用读写锁
17private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
18
19public Object read() {
20 rwLock.readLock().lock();
21 try {
22 return data;
23 } finally {
24 rwLock.readLock().unlock();
25 }
26}
27
28public void write(Object newData) {
29 rwLock.writeLock().lock();
30 try {
31 data = newData;
32 } finally {
33 rwLock.writeLock().unlock();
34 }
35}

5. 使用无锁数据结构

java
1// 不好的做法:使用锁
2private int count = 0;
3
4public synchronized void increment() {
5 count++;
6}
7
8// 优化后:使用原子类
9private AtomicInteger count = new AtomicInteger(0);
10
11public void increment() {
12 count.incrementAndGet();
13}

6. 避免锁嵌套

java
1// 不好的做法:可能死锁
2public synchronized void method1() {
3 synchronized (lock2) {
4 // ...
5 }
6}
7
8public synchronized void method2() {
9 synchronized (lock1) {
10 // ...
11 }
12}
13
14// 优化后:统一加锁顺序或避免嵌套
15public void method1() {
16 synchronized (lock1) {
17 synchronized (lock2) {
18 // ...
19 }
20 }
21}

7. 使用 ConcurrentHashMap 替代 Hashtable

java
1// 不好的做法
2Map<String, String> map = new Hashtable<>();
3
4// 优化后
5Map<String, String> map = new ConcurrentHashMap<>();

8. 使用 ThreadLocal

java
1// 不好的做法:共享变量需要同步
2private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DD");
3
4public synchronized String format(Date date) {
5 return sdf.format(date);
6}
7
8// 优化后:每个线程独立
9private ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(
10 () -> new SimpleDateFormat("yyyy-MM-DD")
11);
12
13public String format(Date date) {
14 return sdf.get().format(date);
15}

9. 使用 StampedLock(读多写少)

java
1// 使用乐观读
2private final StampedLock lock = new StampedLock();
3
4public double read() {
5 long stamp = lock.tryOptimisticRead();
6 double value = this.value;
7
8 if (!lock.validate(stamp)) {
9 stamp = lock.readLock();
10 try {
11 value = this.value;
12 } finally {
13 lock.unlockRead(stamp);
14 }
15 }
16
17 return value;
18}

10. 锁粗化(适当情况下)

java
1// 不好的做法:频繁加锁解锁
2for (int i = 0; i < 1000; i++) {
3 synchronized (lock) {
4 // 操作
5 }
6}
7
8// 优化后:锁粗化
9synchronized (lock) {
10 for (int i = 0; i < 1000; i++) {
11 // 操作
12 }
13}

40. 你了解 Java 中的读写锁吗?

答案:

读写锁(ReadWriteLock)允许多个读线程同时访问,但写线程访问时会阻塞所有读写线程。

核心思想:

  • 读-读:不互斥
  • 读-写:互斥
  • 写-写:互斥

基本使用:

java
1public class Cache {
2 private final Map<String, Object> cache = new HashMap<>();
3 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
4 private final Lock readLock = rwLock.readLock();
5 private final Lock writeLock = rwLock.writeLock();
6
7 // 读操作
8 public Object get(String key) {
9 readLock.lock();
10 try {
11 return cache.get(key);
12 } finally {
13 readLock.unlock();
14 }
15 }
16
17 // 写操作
18 public void put(String key, Object value) {
19 writeLock.lock();
20 try {
21 cache.put(key, value);
22 } finally {
23 writeLock.unlock();
24 }
25 }
26}

特性:

1. 公平性

java
1// 非公平锁(默认)
2ReadWriteLock rwLock = new ReentrantReadWriteLock();
3
4// 公平锁
5ReadWriteLock fairRwLock = new ReentrantReadWriteLock(true);

2. 可重入

java
1// 同一线程可以多次获取读锁或写锁
2readLock.lock();
3readLock.lock(); // 可重入
4try {
5 // ...
6} finally {
7 readLock.unlock();
8 readLock.unlock();
9}

3. 锁降级

java
1// 支持写锁降级为读锁
2writeLock.lock();
3try {
4 // 写操作
5
6 readLock.lock(); // 获取读锁
7} finally {
8 writeLock.unlock(); // 释放写锁,降级为读锁
9}
10
11try {
12 // 读操作
13} finally {
14 readLock.unlock();
15}

4. 不支持锁升级

java
1// 不支持读锁升级为写锁
2readLock.lock();
3try {
4 writeLock.lock(); // 会死锁!
5 // ...
6} finally {
7 readLock.unlock();
8}

应用场景:

1. 缓存系统

java
1public class DataCache {
2 private Map<String, Data> cache = new HashMap<>();
3 private ReadWriteLock rwLock = new ReentrantReadWriteLock();
4
5 public Data getData(String key) {
6 rwLock.readLock().lock();
7 try {
8 Data data = cache.get(key);
9 if (data == null) {
10 rwLock.readLock().unlock();
11 rwLock.writeLock().lock();
12 try {
13 // 双重检查
14 data = cache.get(key);
15 if (data == null) {
16 data = loadFromDatabase(key);
17 cache.put(key, data);
18 }
19 rwLock.readLock().lock();
20 } finally {
21 rwLock.writeLock().unlock();
22 }
23 }
24 return data;
25 } finally {
26 rwLock.readLock().unlock();
27 }
28 }
29}

2. 配置管理

java
1public class ConfigManager {
2 private Properties config = new Properties();
3 private ReadWriteLock rwLock = new ReentrantReadWriteLock();
4
5 public String getProperty(String key) {
6 rwLock.readLock().lock();
7 try {
8 return config.getProperty(key);
9 } finally {
10 rwLock.readLock().unlock();
11 }
12 }
13
14 public void reload() {
15 rwLock.writeLock().lock();
16 try {
17 config = loadConfig();
18 } finally {
19 rwLock.writeLock().unlock();
20 }
21 }
22}

性能对比:

场景synchronizedReadWriteLock
读多写少性能较低性能高
读少写多性能相当性能相当
只读性能较低性能高

最佳实践:

  • 读多写少场景使用读写锁
  • 读写比例相当时使用普通锁
  • 考虑使用 StampedLock(性能更好)

41. 什么是 Java 内存模型(JMM)?

答案:

Java 内存模型(Java Memory Model,JMM)是一种规范,定义了 Java 程序中各个变量的访问规则,以及在 JVM 中将变量存储到内存和从内存中读取变量的底层细节。

核心概念:

1. 主内存与工作内存

1线程1 线程2 线程3
2工作内存 工作内存 工作内存
3 ↓↑ ↓↑ ↓↑
4 ←←←←←←←←←←←←←←←←←←←←←→→→→→→→→→→→→→→→→→→→→
5 主内存
6 (共享变量存储位置)
  • 主内存:所有线程共享,存储共享变量
  • 工作内存:每个线程私有,存储主内存变量的副本

2. 内存交互操作

8 种原子操作:

  • lock(锁定):作用于主内存,标识变量为线程独占
  • unlock(解锁):作用于主内存,释放锁定的变量
  • read(读取):作用于主内存,把变量值传输到工作内存
  • load(载入):作用于工作内存,把 read 的值放入工作内存副本
  • use(使用):作用于工作内存,把值传递给执行引擎
  • assign(赋值):作用于工作内存,把执行引擎的值赋给工作内存变量
  • store(存储):作用于工作内存,把值传送到主内存
  • write(写入):作用于主内存,把 store 的值写入主内存变量

3. JMM 三大特性

原子性(Atomicity):

java
1// 原子操作
2int a = 10; // 原子
3a = b; // 非原子(读b + 写a)
4a++; // 非原子(读a + 加1 + 写a)
5a = a + 1; // 非原子
6
7// 保证原子性
8synchronized (lock) {
9 a++;
10}
11
12AtomicInteger count = new AtomicInteger(0);
13count.incrementAndGet();

可见性(Visibility):

java
1// 问题:线程2可能看不到线程1的修改
2private boolean flag = false;
3
4// 线程1
5flag = true;
6
7// 线程2
8while (!flag) {
9 // 可能一直循环
10}
11
12// 解决方案1:volatile
13private volatile boolean flag = false;
14
15// 解决方案2:synchronized
16synchronized (lock) {
17 flag = true;
18}

有序性(Ordering):

java
1// 可能发生指令重排
2int a = 0;
3boolean flag = false;
4
5// 线程1
6a = 1; // 操作1
7flag = true; // 操作2(可能重排到操作1之前)
8
9// 线程2
10if (flag) {
11 int b = a; // 可能 b = 0
12}
13
14// 解决方案:volatile 禁止重排
15private volatile boolean flag = false;

4. happens-before 规则

如果操作 A happens-before 操作 B,则 A 的结果对 B 可见。

规则列表:

  1. 程序顺序规则:单线程内,前面的操作 happens-before 后面的操作
  2. 锁规则:unlock happens-before 后续对同一个锁的 lock
  3. volatile 规则:volatile 写 happens-before 后续对该变量的读
  4. 传递性:A happens-before B,B happens-before C,则 A happens-before C
  5. 线程启动规则:Thread.start() happens-before 线程内的所有操作
  6. 线程终止规则:线程内所有操作 happens-before Thread.join() 返回
  7. 中断规则:interrupt() happens-before 检测到中断事件
  8. 对象终结规则:构造函数结束 happens-before finalize() 方法

示例:

java
1// 程序顺序规则
2int a = 1; // 操作1
3int b = 2; // 操作2 happens-before 操作1
4
5// 锁规则
6synchronized (lock) {
7 a = 1; // 操作1
8}
9// unlock happens-before 下一个 lock
10
11synchronized (lock) {
12 int b = a; // 能看到 a = 1
13}
14
15// volatile 规则
16volatile boolean flag = false;
17int a = 0;
18
19// 线程1
20a = 1;
21flag = true; // volatile 写
22
23// 线程2
24if (flag) { // volatile 读
25 int b = a; // 一定能看到 a = 1
26}

5. 内存屏障

JMM 通过内存屏障(Memory Barrier)实现:

  • LoadLoad 屏障:Load1; LoadLoad; Load2(确保 Load1 先于 Load2)
  • StoreStore 屏障:Store1; StoreStore; Store2(确保 Store1 先于 Store2)
  • LoadStore 屏障:Load1; LoadStore; Store2(确保 Load1 先于 Store2)
  • StoreLoad 屏障:Store1; StoreLoad; Load2(确保 Store1 先于 Load2)

volatile 的内存屏障:

java
1// volatile 写
2StoreStore 屏障
3volatile 写操作
4StoreLoad 屏障
5
6// volatile 读
7volatile 读操作
8LoadLoad 屏障
9LoadStore 屏障

42. 什么是 Java 中的原子性、可见性和有序性?

答案:

这是并发编程的三大特性,JMM 的核心内容。

1. 原子性(Atomicity)

定义: 一个操作或多个操作要么全部执行成功,要么全部不执行,中间不会被打断。

原子操作:

java
1// 原子操作
2int a = 10; // 赋值操作
3int b = a; // 非原子(读a + 写b)
4a++; // 非原子(读a + 加1 + 写a)
5long c = 0L; // 非原子(64位,分两次写入)

保证原子性的方式:

java
1// 1. synchronized
2private int count = 0;
3
4public synchronized void increment() {
5 count++; // 保证原子性
6}
7
8// 2. Lock
9private final Lock lock = new ReentrantLock();
10
11public void increment() {
12 lock.lock();
13 try {
14 count++;
15 } finally {
16 lock.unlock();
17 }
18}
19
20// 3. 原子类
21private AtomicInteger count = new AtomicInteger(0);
22
23public void increment() {
24 count.incrementAndGet(); // 原子操作
25}

2. 可见性(Visibility)

定义: 当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

可见性问题:

java
1// 线程1
2private boolean flag = false;
3
4public void writer() {
5 flag = true; // 写入工作内存,可能不会立即刷新到主内存
6}
7
8// 线程2
9public void reader() {
10 while (!flag) { // 从工作内存读取,可能读到旧值
11 // 可能一直循环
12 }
13}

保证可见性的方式:

java
1// 1. volatile
2private volatile boolean flag = false;
3
4// 2. synchronized
5private boolean flag = false;
6
7public synchronized void writer() {
8 flag = true; // 解锁时刷新到主内存
9}
10
11public synchronized void reader() {
12 if (flag) { // 加锁时从主内存读取
13 // ...
14 }
15}
16
17// 3. final
18private final int value = 10; // final 变量保证可见性
19
20// 4. Lock
21private final Lock lock = new ReentrantLock();
22
23public void writer() {
24 lock.lock();
25 try {
26 flag = true;
27 } finally {
28 lock.unlock(); // 解锁时刷新
29 }
30}

3. 有序性(Ordering)

定义: 程序执行的顺序按照代码的先后顺序执行。

指令重排问题:

java
1// 单例模式的双重检查锁定
2public class Singleton {
3 private static Singleton instance;
4
5 public static Singleton getInstance() {
6 if (instance == null) { // 第一次检查
7 synchronized (Singleton.class) {
8 if (instance == null) { // 第二次检查
9 instance = new Singleton(); // 可能发生指令重排
10 // 1. 分配内存
11 // 2. 初始化对象
12 // 3. 将 instance 指向内存
13 // 可能重排为:1 -> 3 -> 2
14 }
15 }
16 }
17 return instance;
18 }
19}
20
21// 问题:线程A执行到3,线程B看到 instance != null,但对象未初始化

保证有序性的方式:

java
1// 1. volatile(禁止指令重排)
2private static volatile Singleton instance;
3
4// 2. synchronized(happens-before 规则)
5synchronized (lock) {
6 a = 1;
7 b = 2; // 不会重排到 synchronized 块外
8}
9
10// 3. happens-before 规则
11// 程序顺序规则、锁规则、volatile 规则等

三者关系:

特性volatilesynchronizedLockAtomic
原子性
可见性
有序性

实际应用:

java
1public class Counter {
2 // 可见性:volatile
3 private volatile boolean running = true;
4
5 // 原子性:AtomicInteger
6 private AtomicInteger count = new AtomicInteger(0);
7
8 // 有序性 + 原子性:synchronized
9 private int value = 0;
10
11 public synchronized void updateValue() {
12 value++; // 保证原子性和有序性
13 }
14
15 public void stop() {
16 running = false; // 保证可见性
17 }
18
19 public void increment() {
20 count.incrementAndGet(); // 保证原子性
21 }
22}

43. 什么是 Java 的 happens-before 规则?

答案:

happens-before 是 JMM 中定义的两个操作之间的偏序关系,用于保证内存可见性。

定义: 如果操作 A happens-before 操作 B,那么 A 的执行结果对 B 可见,且 A 的执行顺序在 B 之前。

8 大 happens-before 规则:

1. 程序顺序规则(Program Order Rule)

java
1// 单线程内,前面的操作 happens-before 后面的操作
2int a = 1; // 操作1
3int b = 2; // 操作2 happens-before 操作1
4int c = a + b; // 能看到 a=1, b=2

2. 锁规则(Monitor Lock Rule)

java
1// 对一个锁的解锁 happens-before 后续对这个锁的加锁
2synchronized (lock) {
3 a = 1; // 操作1
4} // unlock
5
6// 另一个线程
7synchronized (lock) { // lock
8 int b = a; // 操作2,能看到 a=1
9}

3. volatile 变量规则(Volatile Variable Rule)

java
1// volatile 写 happens-before 后续对该变量的读
2volatile boolean flag = false;
3int a = 0;
4
5// 线程1
6a = 1; // 操作1
7flag = true; // volatile 写,操作2
8
9// 线程2
10if (flag) { // volatile 读,操作3
11 int b = a; // 操作4,能看到 a=1
12}
13// 操作2 happens-before 操作3
14// 操作1 happens-before 操作2(程序顺序规则)
15// 操作3 happens-before 操作4(程序顺序规则)
16// 因此操作1 happens-before 操作4

4. 传递性规则(Transitivity)

java
1// A happens-before B,B happens-before C
2// 则 A happens-before C
3int a = 1; // 操作A
4int b = a; // 操作B
5int c = b; // 操作C
6// A happens-before B happens-before C

5. 线程启动规则(Thread Start Rule)

java
1// Thread.start() happens-before 线程内的所有操作
2int a = 1; // 操作1
3
4Thread thread = new Thread(() -> {
5 int b = a; // 操作2,能看到 a=1
6});
7
8thread.start(); // happens-before 操作2

6. 线程终止规则(Thread Termination Rule)

java
1// 线程内所有操作 happens-before Thread.join() 返回
2Thread thread = new Thread(() -> {
3 a = 1; // 操作1
4});
5
6thread.start();
7thread.join(); // 等待线程结束
8
9int b = a; // 操作2,能看到 a=1
10// 操作1 happens-before join() 返回

7. 线程中断规则(Thread Interruption Rule)

java
1// interrupt() happens-before 检测到中断事件
2Thread thread = new Thread(() -> {
3 while (!Thread.interrupted()) { // 操作2
4 // 能检测到中断
5 }
6});
7
8thread.start();
9thread.interrupt(); // 操作1 happens-before 操作2

8. 对象终结规则(Finalizer Rule)

java
1// 对象的构造函数结束 happens-before finalize() 方法
2public class MyObject {
3 private int value;
4
5 public MyObject() {
6 value = 10; // 操作1
7 } // 构造函数结束
8
9 @Override
10 protected void finalize() {
11 int a = value; // 操作2,能看到 value=10
12 }
13}

实际应用示例:

1. 双重检查锁定(DCL)

java
1public class Singleton {
2 private static volatile Singleton instance;
3
4 public static Singleton getInstance() {
5 if (instance == null) {
6 synchronized (Singleton.class) {
7 if (instance == null) {
8 instance = new Singleton();
9 // volatile 写 happens-before 后续读
10 }
11 }
12 }
13 return instance;
14 }
15}

2. 生产者-消费者

java
1public class ProducerConsumer {
2 private volatile boolean ready = false;
3 private int data;
4
5 // 生产者
6 public void produce() {
7 data = 42; // 操作1
8 ready = true; // volatile 写,操作2
9 }
10
11 // 消费者
12 public void consume() {
13 if (ready) { // volatile 读,操作3
14 int value = data; // 操作4,能看到 data=42
15 }
16 }
17}

3. 线程安全的延迟初始化

java
1public class LazyInit {
2 private volatile Helper helper;
3
4 public Helper getHelper() {
5 Helper h = helper;
6 if (h == null) {
7 synchronized (this) {
8 h = helper;
9 if (h == null) {
10 h = new Helper();
11 helper = h; // volatile 写
12 }
13 }
14 }
15 return h; // volatile 读,能看到完整初始化的对象
16 }
17}

总结:

  • happens-before 保证内存可见性
  • 不需要显式同步也能保证可见性
  • 是 JMM 的核心概念
  • 理解 happens-before 有助于编写正确的并发代码

44. 什么是 Java 中的指令重排?

答案:

指令重排是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

三种重排:

1. 编译器优化重排

java
1// 原始代码
2int a = 1;
3int b = 2;
4int c = a + b;
5
6// 编译器可能重排为
7int b = 2;
8int a = 1;
9int c = a + b;

2. 指令级并行重排

java
1// CPU 可能并行执行不相关的指令
2int a = 1; // 指令1
3int b = 2; // 指令2(可能与指令1并行)

3. 内存系统重排

java
1// 写缓冲区可能导致写操作重排
2a = 1; // 写入缓冲区
3b = 2; // 可能先刷新到内存

重排的原则:

as-if-serial 语义:

  • 单线程内,重排后的执行结果与顺序执行的结果一致
  • 不会对存在数据依赖的操作进行重排
java
1// 有数据依赖,不会重排
2int a = 1;
3int b = a + 1; // 依赖 a,不会重排到 a=1 之前
4
5// 无数据依赖,可能重排
6int a = 1;
7int b = 2; // 可能重排

重排导致的问题:

1. 双重检查锁定问题

java
1public class Singleton {
2 private static Singleton instance;
3
4 public static Singleton getInstance() {
5 if (instance == null) {
6 synchronized (Singleton.class) {
7 if (instance == null) {
8 instance = new Singleton();
9 // 正常顺序:
10 // 1. 分配内存
11 // 2. 初始化对象
12 // 3. instance 指向内存
13
14 // 重排后可能:
15 // 1. 分配内存
16 // 3. instance 指向内存
17 // 2. 初始化对象
18 }
19 }
20 }
21 return instance; // 可能返回未初始化的对象
22 }
23}
24
25// 解决方案:volatile
26private static volatile Singleton instance;

2. 可见性问题

java
1// 线程1
2int a = 0;
3boolean flag = false;
4
5public void writer() {
6 a = 1; // 操作1
7 flag = true; // 操作2(可能重排到操作1之前)
8}
9
10// 线程2
11public void reader() {
12 if (flag) { // 可能看到 flag=true
13 int b = a; // 但 a 可能还是 0
14 }
15}
16
17// 解决方案:volatile
18private volatile boolean flag = false;

3. 初始化安全问题

java
1public class UnsafeInit {
2 private int value;
3 private boolean initialized;
4
5 public UnsafeInit() {
6 value = 10; // 操作1
7 initialized = true; // 操作2(可能重排到操作1之前)
8 }
9
10 public int getValue() {
11 if (initialized) { // 可能看到 initialized=true
12 return value; // 但 value 可能还是 0
13 }
14 return -1;
15 }
16}

禁止重排的方式:

1. volatile

java
1// volatile 写之前的操作不会重排到 volatile 写之后
2// volatile 读之后的操作不会重排到 volatile 读之前
3private volatile boolean flag = false;
4
5public void writer() {
6 a = 1; // 不会重排到 flag=true 之后
7 flag = true; // volatile 写
8}
9
10public void reader() {
11 if (flag) { // volatile 读
12 int b = a; // 不会重排到 flag 读之前
13 }
14}

2. synchronized

java
1// synchronized 块内的操作不会重排到块外
2synchronized (lock) {
3 a = 1;
4 b = 2; // 不会重排到 synchronized 块外
5}

3. final

java
1// final 字段的初始化不会重排到构造函数之外
2public class FinalExample {
3 private final int value;
4
5 public FinalExample() {
6 value = 10; // 不会重排到构造函数之外
7 }
8}

4. happens-before 规则

java
1// 遵循 happens-before 规则的操作不会重排

内存屏障:

JVM 通过插入内存屏障指令来禁止重排:

java
1// volatile 写
2StoreStore 屏障 // 禁止前面的写与 volatile 写重排
3volatile
4StoreLoad 屏障 // 禁止 volatile 写与后面的读/写重排
5
6// volatile 读
7volatile
8LoadLoad 屏障 // 禁止 volatile 读与后面的读重排
9LoadStore 屏障 // 禁止 volatile 读与后面的写重排

最佳实践:

  1. 使用 volatile 保证可见性和有序性
  2. 使用 synchronized 或 Lock 保证原子性和有序性
  3. 理解 happens-before 规则
  4. 避免依赖指令执行顺序
  5. 使用不可变对象

45. Java 中的 final 关键字是否能保证变量的可见性?

答案:

是的,final 关键字能保证变量的可见性,但有特定的条件和限制。

final 的可见性保证:

1. final 字段的初始化安全

java
1public class FinalExample {
2 private final int value;
3 private final String name;
4
5 public FinalExample() {
6 value = 10;
7 name = "test";
8 // 构造函数结束时,final 字段对其他线程可见
9 }
10}
11
12// 线程1
13FinalExample obj = new FinalExample();
14
15// 线程2
16int v = obj.value; // 一定能看到 value=10
17String n = obj.name; // 一定能看到 name="test"

2. final 的 happens-before 规则

java
1// 对象的构造函数中对 final 字段的写入
2// happens-before
3// 在构造函数外对该对象的 final 字段的读取
4
5public class SafePublication {
6 private final int value;
7
8 public SafePublication() {
9 value = 42; // final 写
10 } // 构造函数结束
11
12 public int getValue() {
13 return value; // final 读,一定能看到 42
14 }
15}

3. final 引用对象的可见性

java
1public class FinalReference {
2 private final int[] array;
3
4 public FinalReference() {
5 array = new int[10];
6 array[0] = 1; // 对 final 引用对象的修改也可见
7 }
8
9 public int getFirst() {
10 return array[0]; // 一定能看到 array[0]=1
11 }
12}

final 的限制:

1. 不保证非 final 字段的可见性

java
1public class MixedFields {
2 private final int finalValue;
3 private int normalValue;
4
5 public MixedFields() {
6 finalValue = 10; // 保证可见
7 normalValue = 20; // 不保证可见
8 }
9}
10
11// 其他线程可能看到 finalValue=10,但 normalValue=0

2. 不保证 final 字段修改后的可见性

java
1public class MutableFinal {
2 private final List<String> list = new ArrayList<>();
3
4 public void add(String item) {
5 list.add(item); // 修改 final 引用的对象,不保证可见性
6 }
7}
8
9// 解决方案:使用 volatile 或同步
10private final List<String> list = new CopyOnWriteArrayList<>();

3. 构造函数中的 this 逃逸

java
1// 错误示例:this 逃逸
2public class ThisEscape {
3 private final int value;
4
5 public ThisEscape() {
6 // 在构造函数中将 this 传递出去
7 EventListener listener = new EventListener() {
8 public void onEvent(Event e) {
9 doSomething(e);
10 }
11 };
12
13 source.registerListener(listener); // this 逃逸
14
15 value = 42; // final 字段的可见性保证失效
16 }
17}
18
19// 正确做法:构造函数完成后再注册
20public class SafeConstruction {
21 private final int value;
22
23 private SafeConstruction() {
24 value = 42;
25 }
26
27 public static SafeConstruction newInstance() {
28 SafeConstruction obj = new SafeConstruction();
29 source.registerListener(obj); // 构造完成后注册
30 return obj;
31 }
32}

final 与 volatile 的对比:

特性finalvolatile
可见性保证(初始化)保证(所有操作)
有序性保证(初始化)保证(所有操作)
可修改不可修改可修改
性能无开销有轻微开销
适用场景不可变字段可变状态标志

实际应用:

1. 不可变对象

java
1public final class ImmutablePoint {
2 private final int x;
3 private final int y;
4
5 public ImmutablePoint(int x, int y) {
6 this.x = x;
7 this.y = y;
8 }
9
10 public int getX() { return x; }
11 public int getY() { return y; }
12}
13// 线程安全,无需同步

2. 安全发布

java
1public class SafePublisher {
2 private final Map<String, String> config;
3
4 public SafePublisher() {
5 Map<String, String> temp = new HashMap<>();
6 temp.put("key1", "value1");
7 temp.put("key2", "value2");
8 config = Collections.unmodifiableMap(temp);
9 // final 保证 config 对其他线程可见
10 }
11}

3. 单例模式

java
1public class Singleton {
2 private static final Singleton INSTANCE = new Singleton();
3
4 private Singleton() {
5 // 初始化
6 }
7
8 public static Singleton getInstance() {
9 return INSTANCE; // final 保证可见性
10 }
11}

最佳实践:

  1. 尽可能使用 final 修饰不可变字段
  2. 避免构造函数中的 this 逃逸
  3. final 引用的可变对象仍需同步
  4. 结合不可变模式使用
  5. 理解 final 的可见性保证范围

46. 为什么在 Java 中需要使用 ThreadLocal?

答案:

ThreadLocal 提供线程局部变量,每个线程都有自己独立的变量副本,互不干扰。

使用场景:

1. 线程安全的日期格式化

java
1// 问题:SimpleDateFormat 不是线程安全的
2private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
3
4public String format(Date date) {
5 return sdf.format(date); // 多线程下会出错
6}
7
8// 解决方案:ThreadLocal
9private static final ThreadLocal<SimpleDateFormat> sdf =
10 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
11
12public String format(Date date) {
13 return sdf.get().format(date); // 每个线程独立
14}

2. 数据库连接管理

java
1public class ConnectionManager {
2 private static final ThreadLocal<Connection> connectionHolder =
3 new ThreadLocal<>();
4
5 public static Connection getConnection() {
6 Connection conn = connectionHolder.get();
7 if (conn == null) {
8 conn = DriverManager.getConnection(url, user, password);
9 connectionHolder.set(conn);
10 }
11 return conn;
12 }
13
14 public static void closeConnection() {
15 Connection conn = connectionHolder.get();
16 if (conn != null) {
17 conn.close();
18 connectionHolder.remove(); // 清理
19 }
20 }
21}

3. 用户上下文传递

java
1public class UserContext {
2 private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
3
4 public static void setUser(User user) {
5 currentUser.set(user);
6 }
7
8 public static User getUser() {
9 return currentUser.get();
10 }
11
12 public static void clear() {
13 currentUser.remove();
14 }
15}
16
17// 在过滤器中设置
18public class AuthFilter implements Filter {
19 public void doFilter(ServletRequest request, ServletResponse response,
20 FilterChain chain) {
21 User user = authenticate(request);
22 UserContext.setUser(user);
23 try {
24 chain.doFilter(request, response);
25 } finally {
26 UserContext.clear(); // 清理
27 }
28 }
29}

4. 事务管理

java
1public class TransactionManager {
2 private static final ThreadLocal<Transaction> transactionHolder =
3 new ThreadLocal<>();
4
5 public static void beginTransaction() {
6 Transaction tx = new Transaction();
7 transactionHolder.set(tx);
8 }
9
10 public static Transaction getCurrentTransaction() {
11 return transactionHolder.get();
12 }
13
14 public static void commit() {
15 Transaction tx = transactionHolder.get();
16 if (tx != null) {
17 tx.commit();
18 transactionHolder.remove();
19 }
20 }
21}

优点:

  1. 线程安全,无需同步
  2. 避免参数传递
  3. 性能好,无锁竞争

缺点:

  1. 内存泄漏风险(需要手动清理)
  2. 不适合线程池场景(线程复用)
  3. 增加内存开销

47. Java 中的 ThreadLocal 是如何实现线程资源隔离的?

答案:

ThreadLocal 通过在每个线程内部维护一个 Map 来实现资源隔离。

实现原理:

1. 数据结构

java
1// Thread 类中的字段
2public class Thread {
3 ThreadLocal.ThreadLocalMap threadLocals = null;
4}
5
6// ThreadLocalMap 是 ThreadLocal 的内部类
7static class ThreadLocalMap {
8 static class Entry extends WeakReference<ThreadLocal<?>> {
9 Object value;
10
11 Entry(ThreadLocal<?> k, Object v) {
12 super(k);
13 value = v;
14 }
15 }
16
17 private Entry[] table; // 哈希表
18}

2. set 方法

java
1public void set(T value) {
2 Thread t = Thread.currentThread(); // 获取当前线程
3 ThreadLocalMap map = getMap(t); // 获取线程的 Map
4 if (map != null)
5 map.set(this, value); // 以 ThreadLocal 为 key
6 else
7 createMap(t, value); // 创建 Map
8}
9
10ThreadLocalMap getMap(Thread t) {
11 return t.threadLocals;
12}

3. get 方法

java
1public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 return (T)e.value;
8 }
9 }
10 return setInitialValue(); // 初始化
11}

4. remove 方法

java
1public void remove() {
2 ThreadLocalMap m = getMap(Thread.currentThread());
3 if (m != null)
4 m.remove(this);
5}

内存结构:

1Thread-1
2 └── threadLocals (ThreadLocalMap)
3 ├── Entry[0]: ThreadLocal1 -> Value1
4 ├── Entry[1]: ThreadLocal2 -> Value2
5 └── Entry[2]: null
6
7Thread-2
8 └── threadLocals (ThreadLocalMap)
9 ├── Entry[0]: ThreadLocal1 -> Value3
10 └── Entry[1]: ThreadLocal2 -> Value4

哈希冲突解决:

java
1// 使用线性探测法
2private void set(ThreadLocal<?> key, Object value) {
3 Entry[] tab = table;
4 int len = tab.length;
5 int i = key.threadLocalHashCode & (len-1); // 计算索引
6
7 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
8 ThreadLocal<?> k = e.get();
9
10 if (k == key) {
11 e.value = value; // 更新
12 return;
13 }
14
15 if (k == null) {
16 replaceStaleEntry(key, value, i); // 替换过期 Entry
17 return;
18 }
19 }
20
21 tab[i] = new Entry(key, value); // 插入新 Entry
22 int sz = ++size;
23 if (!cleanSomeSlots(i, sz) && sz >= threshold)
24 rehash(); // 扩容
25}

示例:

java
1public class ThreadLocalDemo {
2 private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
3
4 public static void main(String[] args) {
5 // 线程1
6 new Thread(() -> {
7 threadLocal.set(100);
8 System.out.println("Thread-1: " + threadLocal.get()); // 100
9 }).start();
10
11 // 线程2
12 new Thread(() -> {
13 threadLocal.set(200);
14 System.out.println("Thread-2: " + threadLocal.get()); // 200
15 }).start();
16
17 // 主线程
18 threadLocal.set(300);
19 System.out.println("Main: " + threadLocal.get()); // 300
20 }
21}

48. 为什么 Java 中的 ThreadLocal 对 key 的引用为弱引用?

答案:

ThreadLocal 使用弱引用作为 key 是为了避免内存泄漏。

弱引用的定义:

java
1static class Entry extends WeakReference<ThreadLocal<?>> {
2 Object value;
3
4 Entry(ThreadLocal<?> k, Object v) {
5 super(k); // ThreadLocal 作为弱引用
6 value = v; // value 是强引用
7 }
8}

引用关系:

1栈中的引用 → ThreadLocal 对象 ← 弱引用 ← Entry
2
3 Value(强引用)

为什么使用弱引用:

1. 避免 ThreadLocal 对象无法回收

java
1// 如果使用强引用
2public void method() {
3 ThreadLocal<String> local = new ThreadLocal<>();
4 local.set("value");
5 // method 结束后,local 变量被回收
6 // 但如果 Entry 持有 ThreadLocal 的强引用
7 // ThreadLocal 对象无法被 GC 回收
8}
9
10// 使用弱引用
11// method 结束后,local 变量被回收
12// ThreadLocal 对象没有强引用,可以被 GC 回收
13// Entry 的 key 变为 null

2. 自动清理机制

java
1// ThreadLocalMap 在操作时会清理 key 为 null 的 Entry
2private int expungeStaleEntry(int staleSlot) {
3 Entry[] tab = table;
4 int len = tab.length;
5
6 // 清理当前位置
7 tab[staleSlot].value = null;
8 tab[staleSlot] = null;
9 size--;
10
11 // 清理后续位置的过期 Entry
12 Entry e;
13 int i;
14 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null;
15 i = nextIndex(i, len)) {
16 ThreadLocal<?> k = e.get();
17 if (k == null) {
18 e.value = null;
19 tab[i] = null;
20 size--;
21 } else {
22 // rehash
23 }
24 }
25 return i;
26}

内存泄漏问题:

问题场景:

java
1// ThreadLocal 被回收,key 变为 null
2// 但 value 仍然被 Entry 强引用
3// 如果线程长期存活(如线程池),value 无法回收
4
5ThreadLocal<byte[]> local = new ThreadLocal<>();
6local.set(new byte[1024 * 1024]); // 1MB
7local = null; // ThreadLocal 被回收
8
9// Entry: key=null, value=1MB 数据
10// value 无法被回收,造成内存泄漏

解决方案:

java
1// 1. 手动调用 remove()
2ThreadLocal<String> local = new ThreadLocal<>();
3try {
4 local.set("value");
5 // 使用 local
6} finally {
7 local.remove(); // 清理
8}
9
10// 2. 使用 try-with-resources(自定义)
11public class AutoCleanThreadLocal<T> implements AutoCloseable {
12 private ThreadLocal<T> threadLocal = new ThreadLocal<>();
13
14 public void set(T value) {
15 threadLocal.set(value);
16 }
17
18 public T get() {
19 return threadLocal.get();
20 }
21
22 @Override
23 public void close() {
24 threadLocal.remove();
25 }
26}
27
28// 使用
29try (AutoCleanThreadLocal<String> local = new AutoCleanThreadLocal<>()) {
30 local.set("value");
31 // 使用 local
32} // 自动清理

引用类型对比:

引用类型回收时机适用场景
强引用永不回收正常对象
软引用内存不足时缓存
弱引用GC 时ThreadLocal key
虚引用GC 时回收通知

49. Java 中使用 ThreadLocal 的最佳实践是什么?

答案:

1. 使用 static final 修饰

java
1// 推荐:static final
2private static final ThreadLocal<SimpleDateFormat> sdf =
3 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
4
5// 不推荐:实例变量
6private ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<>();

原因:

  • ThreadLocal 通常作为全局变量使用
  • 避免创建多个 ThreadLocal 实例
  • 减少内存开销

2. 及时清理(remove)

java
1// 推荐:使用 try-finally
2ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
3
4public void doWork() {
5 try {
6 Connection conn = getConnection();
7 connectionHolder.set(conn);
8 // 执行业务逻辑
9 } finally {
10 connectionHolder.remove(); // 清理
11 }
12}
13
14// 在过滤器中使用
15public class RequestContextFilter implements Filter {
16 private static final ThreadLocal<RequestContext> contextHolder =
17 new ThreadLocal<>();
18
19 @Override
20 public void doFilter(ServletRequest request, ServletResponse response,
21 FilterChain chain) throws IOException, ServletException {
22 try {
23 contextHolder.set(new RequestContext(request));
24 chain.doFilter(request, response);
25 } finally {
26 contextHolder.remove(); // 清理
27 }
28 }
29}

3. 使用 initialValue 或 withInitial

java
1// 方式1:重写 initialValue
2private static final ThreadLocal<SimpleDateFormat> sdf =
3 new ThreadLocal<SimpleDateFormat>() {
4 @Override
5 protected SimpleDateFormat initialValue() {
6 return new SimpleDateFormat("yyyy-MM-dd");
7 }
8 };
9
10// 方式2:使用 withInitial(推荐)
11private static final ThreadLocal<SimpleDateFormat> sdf =
12 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

4. 避免在线程池中使用

java
1// 问题:线程池中线程复用,ThreadLocal 数据可能污染
2ExecutorService executor = Executors.newFixedThreadPool(10);
3
4executor.submit(() -> {
5 threadLocal.set("value1");
6 // 任务执行完,线程返回线程池
7 // threadLocal 中的数据仍然存在
8});
9
10executor.submit(() -> {
11 String value = threadLocal.get(); // 可能获取到上一个任务的数据
12});
13
14// 解决方案:每次使用前清理
15executor.submit(() -> {
16 try {
17 threadLocal.remove(); // 清理旧数据
18 threadLocal.set("value");
19 // 执行任务
20 } finally {
21 threadLocal.remove(); // 清理
22 }
23});

5. 不要存储大对象

java
1// 不推荐:存储大对象
2private static final ThreadLocal<byte[]> largeData = new ThreadLocal<>();
3largeData.set(new byte[10 * 1024 * 1024]); // 10MB
4
5// 推荐:存储引用或小对象
6private static final ThreadLocal<String> userId = new ThreadLocal<>();
7userId.set("user123");

6. 文档化使用场景

java
1/**
2 * 存储当前请求的用户信息
3 * 注意:必须在请求结束时调用 clear() 清理
4 */
5private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
6
7public static void setUser(User user) {
8 currentUser.set(user);
9}
10
11public static User getUser() {
12 return currentUser.get();
13}
14
15/**
16 * 清理当前线程的用户信息
17 * 必须在请求结束时调用
18 */
19public static void clear() {
20 currentUser.remove();
21}

7. 监控和检测

java
1public class ThreadLocalMonitor {
2 private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
3
4 public static void start() {
5 startTime.set(System.currentTimeMillis());
6 }
7
8 public static void end() {
9 Long start = startTime.get();
10 if (start != null) {
11 long duration = System.currentTimeMillis() - start;
12 System.out.println("Duration: " + duration + "ms");
13 startTime.remove(); // 清理
14 }
15 }
16}

8. 使用包装类

java
1public class ThreadLocalContext<T> {
2 private final ThreadLocal<T> threadLocal;
3
4 public ThreadLocalContext(Supplier<T> supplier) {
5 this.threadLocal = ThreadLocal.withInitial(supplier);
6 }
7
8 public T get() {
9 return threadLocal.get();
10 }
11
12 public void set(T value) {
13 threadLocal.set(value);
14 }
15
16 public void remove() {
17 threadLocal.remove();
18 }
19
20 // 自动清理
21 public void execute(Consumer<T> action) {
22 try {
23 action.accept(get());
24 } finally {
25 remove();
26 }
27 }
28}

50. Java 中的 InheritableThreadLocal 是什么?

答案:

InheritableThreadLocal 是 ThreadLocal 的子类,允许子线程继承父线程的 ThreadLocal 值。

基本使用:

java
1// ThreadLocal:子线程无法访问父线程的值
2private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
3
4public static void main(String[] args) {
5 threadLocal.set("父线程的值");
6
7 new Thread(() -> {
8 System.out.println(threadLocal.get()); // null
9 }).start();
10}
11
12// InheritableThreadLocal:子线程可以访问父线程的值
13private static InheritableThreadLocal<String> inheritableThreadLocal =
14 new InheritableThreadLocal<>();
15
16public static void main(String[] args) {
17 inheritableThreadLocal.set("父线程的值");
18
19 new Thread(() -> {
20 System.out.println(inheritableThreadLocal.get()); // 父线程的值
21 }).start();
22}

实现原理:

java
1// Thread 类中的字段
2public class Thread {
3 ThreadLocal.ThreadLocalMap threadLocals = null;
4 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 可继承的
5}
6
7// Thread 构造函数
8private void init(ThreadGroup g, Runnable target, String name,
9 long stackSize, AccessControlContext acc,
10 boolean inheritThreadLocals) {
11 Thread parent = currentThread();
12
13 // 如果父线程有 inheritableThreadLocals,复制给子线程
14 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
15 this.inheritableThreadLocals =
16 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
17}

自定义继承逻辑:

java
1// 重写 childValue 方法
2private static InheritableThreadLocal<List<String>> inheritableThreadLocal =
3 new InheritableThreadLocal<List<String>>() {
4 @Override
5 protected List<String> childValue(List<String> parentValue) {
6 // 返回父线程值的副本,避免共享
7 return new ArrayList<>(parentValue);
8 }
9 };
10
11public static void main(String[] args) {
12 List<String> list = new ArrayList<>();
13 list.add("item1");
14 inheritableThreadLocal.set(list);
15
16 new Thread(() -> {
17 List<String> childList = inheritableThreadLocal.get();
18 childList.add("item2"); // 不影响父线程的 list
19 System.out.println(childList); // [item1, item2]
20 }).start();
21
22 Thread.sleep(100);
23 System.out.println(list); // [item1]
24}

应用场景:

1. 链路追踪

java
1public class TraceContext {
2 private static final InheritableThreadLocal<String> traceId =
3 new InheritableThreadLocal<>();
4
5 public static void setTraceId(String id) {
6 traceId.set(id);
7 }
8
9 public static String getTraceId() {
10 return traceId.get();
11 }
12}
13
14// 主线程
15TraceContext.setTraceId("trace-123");
16
17// 子线程自动继承
18new Thread(() -> {
19 System.out.println(TraceContext.getTraceId()); // trace-123
20 // 执行业务逻辑
21}).start();

2. 用户上下文传递

java
1public class UserContext {
2 private static final InheritableThreadLocal<User> currentUser =
3 new InheritableThreadLocal<>();
4
5 public static void setUser(User user) {
6 currentUser.set(user);
7 }
8
9 public static User getUser() {
10 return currentUser.get();
11 }
12}
13
14// 主线程设置用户
15UserContext.setUser(new User("张三"));
16
17// 异步任务自动继承
18CompletableFuture.runAsync(() -> {
19 User user = UserContext.getUser(); // 张三
20 // 执行异步任务
21});

局限性:

1. 线程池问题

java
1// 问题:线程池中线程复用,继承的值不会更新
2ExecutorService executor = Executors.newFixedThreadPool(10);
3
4inheritableThreadLocal.set("value1");
5executor.submit(() -> {
6 System.out.println(inheritableThreadLocal.get()); // value1
7});
8
9inheritableThreadLocal.set("value2");
10executor.submit(() -> {
11 // 如果复用了之前的线程,仍然是 value1
12 System.out.println(inheritableThreadLocal.get()); // value1(错误)
13});
14
15// 解决方案:使用 TransmittableThreadLocal(阿里开源)

2. 内存泄漏

java
1// 子线程继承父线程的值,增加内存占用
2// 需要及时清理
3try {
4 inheritableThreadLocal.set(value);
5 // 创建子线程
6} finally {
7 inheritableThreadLocal.remove();
8}

对比总结:

特性ThreadLocalInheritableThreadLocal
子线程继承
性能较好稍差(需要复制)
内存占用较小较大
适用场景线程隔离父子线程传递

51. ThreadLocal 的缺点?

答案:

1. 内存泄漏风险

  • Entry 的 key 是弱引用,但 value 是强引用
  • ThreadLocal 被回收后,value 仍然存在
  • 线程长期存活(如线程池)会导致内存泄漏

2. 线程池场景下的问题

  • 线程复用导致数据污染
  • 上一个任务的数据可能影响下一个任务

3. 增加内存开销

  • 每个线程都有独立的副本
  • 大对象会占用大量内存

4. 不适合父子线程传递

  • 普通 ThreadLocal 无法传递给子线程
  • InheritableThreadLocal 在线程池中失效

5. 调试困难

  • 数据隐藏在线程内部
  • 难以追踪数据流向

6. 容易被滥用

  • 过度使用导致代码难以理解
  • 隐式依赖增加耦合

52. 为什么 Netty 不使用 ThreadLocal 而是自定义了一个 FastThreadLocal?

答案:

Netty 的 FastThreadLocal 解决了 ThreadLocal 的性能问题。

ThreadLocal 的性能问题:

  1. 使用线性探测法解决哈希冲突,性能较差
  2. 需要清理过期 Entry,增加开销
  3. 哈希计算有开销

FastThreadLocal 的优化:

1. 使用数组代替哈希表

java
1// ThreadLocal:使用哈希表
2ThreadLocalMap {
3 Entry[] table; // 哈希表
4 // 需要计算哈希值,处理冲突
5}
6
7// FastThreadLocal:使用数组
8InternalThreadLocalMap {
9 Object[] indexedVariables; // 直接数组访问
10 // 每个 FastThreadLocal 有固定索引
11}

2. 直接索引访问

java
1// FastThreadLocal 有固定索引
2public class FastThreadLocal<V> {
3 private final int index; // 固定索引
4
5 public final V get() {
6 return get(InternalThreadLocalMap.get());
7 }
8
9 public final V get(InternalThreadLocalMap threadLocalMap) {
10 Object v = threadLocalMap.indexedVariable(index); // 直接访问
11 if (v != InternalThreadLocalMap.UNSET) {
12 return (V) v;
13 }
14 return initialize(threadLocalMap);
15 }
16}

3. 配合 FastThreadLocalThread 使用

java
1// Netty 的线程类
2public class FastThreadLocalThread extends Thread {
3 private InternalThreadLocalMap threadLocalMap;
4
5 public final InternalThreadLocalMap threadLocalMap() {
6 return threadLocalMap;
7 }
8
9 public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
10 this.threadLocalMap = threadLocalMap;
11 }
12}

性能对比:

  • ThreadLocal:O(n) 最坏情况(线性探测)
  • FastThreadLocal:O(1) 直接访问

使用示例:

java
1// 定义 FastThreadLocal
2private static final FastThreadLocal<String> context =
3 new FastThreadLocal<String>() {
4 @Override
5 protected String initialValue() {
6 return "default";
7 }
8 };
9
10// 使用
11context.set("value");
12String value = context.get();
13context.remove();

53. 什么是 Java 的 TransmittableThreadLocal?

答案:

TransmittableThreadLocal(TTL)是阿里开源的 ThreadLocal 增强库,解决线程池场景下的上下文传递问题。

问题场景:

java
1// InheritableThreadLocal 在线程池中失效
2InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
3
4context.set("value1");
5executor.submit(() -> {
6 System.out.println(context.get()); // value1
7});
8
9context.set("value2");
10executor.submit(() -> {
11 // 如果复用了之前的线程,仍然是 value1
12 System.out.println(context.get()); // value1(错误)
13});

TTL 解决方案:

java
1// 使用 TransmittableThreadLocal
2TransmittableThreadLocal<String> context =
3 new TransmittableThreadLocal<>();
4
5// 包装线程池
6ExecutorService executor = TtlExecutors.getTtlExecutorService(
7 Executors.newFixedThreadPool(10)
8);
9
10context.set("value1");
11executor.submit(() -> {
12 System.out.println(context.get()); // value1
13});
14
15context.set("value2");
16executor.submit(() -> {
17 System.out.println(context.get()); // value2(正确)
18});

实现原理:

  1. 在任务提交时捕获当前线程的 TTL 值
  2. 在任务执行前恢复 TTL 值
  3. 在任务执行后清理 TTL 值

应用场景:

  • 分布式链路追踪
  • 日志 MDC 传递
  • 用户上下文传递

54. Java 中 Thread.sleep 和 Thread.yield 的区别?

答案:

特性Thread.sleepThread.yield
作用让线程休眠指定时间让出 CPU 时间片
状态TIMED_WAITINGRUNNABLE
不释放锁不释放锁
时间指定休眠时间立即返回
保证至少休眠指定时间不保证让出
异常InterruptedException

Thread.sleep:

java
1public static void sleep(long millis) throws InterruptedException
2
3// 使用
4try {
5 Thread.sleep(1000); // 休眠1秒
6} catch (InterruptedException e) {
7 // 处理中断
8}

Thread.yield:

java
1public static native void yield();
2
3// 使用
4Thread.yield(); // 建议让出 CPU

使用场景:

  • sleep:需要暂停执行一段时间
  • yield:降低线程优先级,让其他线程执行

55. Java 中 Thread.sleep(0) 的作用是什么?

答案:

Thread.sleep(0) 会触发操作系统重新调度线程,但不会让线程进入休眠状态。

作用:

  1. 让出当前时间片
  2. 触发线程调度
  3. 给其他线程执行机会

与 yield 的区别:

  • sleep(0):触发操作系统调度
  • yield():仅建议 JVM 调度

使用场景:

java
1// 在长时间循环中给其他线程机会
2while (running) {
3 // 执行任务
4 doWork();
5
6 // 避免独占 CPU
7 Thread.sleep(0);
8}

56. Java 中什么情况会导致死锁?如何避免?

答案:

死锁的四个必要条件:

  1. 互斥:资源不能被共享
  2. 持有并等待:持有资源的同时等待其他资源
  3. 不可剥夺:资源不能被强制释放
  4. 循环等待:存在资源的循环等待链

死锁示例:

java
1public class DeadlockDemo {
2 private static Object lock1 = new Object();
3 private static Object lock2 = new Object();
4
5 public static void main(String[] args) {
6 // 线程1:先锁 lock1,再锁 lock2
7 new Thread(() -> {
8 synchronized (lock1) {
9 System.out.println("Thread1 获取 lock1");
10 try { Thread.sleep(100); } catch (InterruptedException e) {}
11 synchronized (lock2) {
12 System.out.println("Thread1 获取 lock2");
13 }
14 }
15 }).start();
16
17 // 线程2:先锁 lock2,再锁 lock1
18 new Thread(() -> {
19 synchronized (lock2) {
20 System.out.println("Thread2 获取 lock2");
21 try { Thread.sleep(100); } catch (InterruptedException e) {}
22 synchronized (lock1) {
23 System.out.println("Thread2 获取 lock1");
24 }
25 }
26 }).start();
27 }
28}

避免死锁的方法:

1. 固定加锁顺序

java
1// 统一加锁顺序
2public void transfer(Account from, Account to, int amount) {
3 Account first = from.id < to.id ? from : to;
4 Account second = from.id < to.id ? to : from;
5
6 synchronized (first) {
7 synchronized (second) {
8 from.debit(amount);
9 to.credit(amount);
10 }
11 }
12}

2. 使用超时

java
1Lock lock1 = new ReentrantLock();
2Lock lock2 = new ReentrantLock();
3
4public void method() {
5 try {
6 if (lock1.tryLock(1, TimeUnit.SECONDS)) {
7 try {
8 if (lock2.tryLock(1, TimeUnit.SECONDS)) {
9 try {
10 // 执行操作
11 } finally {
12 lock2.unlock();
13 }
14 }
15 } finally {
16 lock1.unlock();
17 }
18 }
19 } catch (InterruptedException e) {
20 // 处理中断
21 }
22}

3. 死锁检测

java
1// 使用 JMX 检测死锁
2ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
3long[] deadlockedThreads = tmx.findDeadlockedThreads();
4if (deadlockedThreads != null) {
5 ThreadInfo[] threadInfos = tmx.getThreadInfo(deadlockedThreads);
6 // 处理死锁
7}

57. Java 中 volatile 关键字的作用是什么?

答案:

volatile 保证变量的可见性和有序性,但不保证原子性。

作用:

  1. 保证可见性:修改立即对其他线程可见
  2. 禁止指令重排:保证有序性
  3. 不保证原子性:复合操作不是原子的

使用场景:

java
1// 1. 状态标志
2private volatile boolean running = true;
3
4public void stop() {
5 running = false;
6}
7
8// 2. 双重检查锁定
9private volatile static Singleton instance;
10
11// 3. 独立观察
12private volatile long lastUpdateTime;

58. 什么是 Java 中的 ABA 问题?

答案:

ABA 问题是指一个值从 A 变成 B,再变回 A,CAS 操作无法检测到这个变化。

问题示例:

java
1AtomicInteger value = new AtomicInteger(100);
2
3// 线程1:读取值 100
4int oldValue = value.get(); // 100
5
6// 线程2:100 -> 200 -> 100
7value.compareAndSet(100, 200);
8value.compareAndSet(200, 100);
9
10// 线程1:CAS 成功,但值已经被修改过
11value.compareAndSet(oldValue, 101); // 成功

解决方案:

java
1// 使用 AtomicStampedReference(版本号)
2AtomicStampedReference<Integer> ref =
3 new AtomicStampedReference<>(100, 0);
4
5int stamp = ref.getStamp();
6ref.compareAndSet(100, 101, stamp, stamp + 1);

59. 在 Java 中主线程如何知晓创建的子线程是否执行成功?

答案:

1. 使用 join()

java
1Thread thread = new Thread(() -> {
2 // 执行任务
3});
4thread.start();
5thread.join(); // 等待线程结束

2. 使用 Future

java
1ExecutorService executor = Executors.newSingleThreadExecutor();
2Future<String> future = executor.submit(() -> {
3 return "结果";
4});
5
6try {
7 String result = future.get(); // 获取结果
8 System.out.println("成功: " + result);
9} catch (ExecutionException e) {
10 System.out.println("失败: " + e.getCause());
11}

3. 使用 CompletableFuture

java
1CompletableFuture.supplyAsync(() -> {
2 return "结果";
3}).whenComplete((result, ex) -> {
4 if (ex != null) {
5 System.out.println("失败: " + ex);
6 } else {
7 System.out.println("成功: " + result);
8 }
9});

4. 使用 CountDownLatch

java
1CountDownLatch latch = new CountDownLatch(1);
2AtomicBoolean success = new AtomicBoolean(false);
3
4new Thread(() -> {
5 try {
6 // 执行任务
7 success.set(true);
8 } finally {
9 latch.countDown();
10 }
11}).start();
12
13latch.await();
14System.out.println("成功: " + success.get());

60. 创建线程池有哪些方式?

答案:

1. Executors 工厂方法

java
1ExecutorService executor1 = Executors.newFixedThreadPool(5);
2ExecutorService executor2 = Executors.newCachedThreadPool();
3ExecutorService executor3 = Executors.newSingleThreadExecutor();
4ScheduledExecutorService executor4 = Executors.newScheduledThreadPool(5);

2. ThreadPoolExecutor 构造函数(推荐)

java
1ThreadPoolExecutor executor = new ThreadPoolExecutor(
2 5, // 核心线程数
3 10, // 最大线程数
4 60L, // 空闲线程存活时间
5 TimeUnit.SECONDS,
6 new LinkedBlockingQueue<>(100),
7 Executors.defaultThreadFactory(),
8 new ThreadPoolExecutor.AbortPolicy()
9);

3. Spring 配置

java
1@Configuration
2public class ThreadPoolConfig {
3 @Bean
4 public Executor taskExecutor() {
5 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
6 executor.setCorePoolSize(5);
7 executor.setMaxPoolSize(10);
8 executor.setQueueCapacity(100);
9 executor.setThreadNamePrefix("async-");
10 executor.initialize();
11 return executor;
12 }
13}

61. 线程安全的集合有哪些?

答案:

1. 同步集合(synchronized)

java
1Vector<String> vector = new Vector<>();
2Hashtable<String, String> hashtable = new Hashtable<>();
3Stack<String> stack = new Stack<>();

2. 并发集合(java.util.concurrent)

java
1// Map
2ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
3
4// List
5CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
6
7// Set
8CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
9ConcurrentSkipListSet<String> sortedSet = new ConcurrentSkipListSet<>();
10
11// Queue
12ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
13LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();

3. Collections 工具类

java
1List<String> syncList = Collections.synchronizedList(new ArrayList<>());
2Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
3Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

62. Java 创建线程有哪些方式?

答案:

1. 继承 Thread 类

java
1class MyThread extends Thread {
2 @Override
3 public void run() {
4 System.out.println("Thread running");
5 }
6}
7
8new MyThread().start();

2. 实现 Runnable 接口

java
1class MyRunnable implements Runnable {
2 @Override
3 public void run() {
4 System.out.println("Runnable running");
5 }
6}
7
8new Thread(new MyRunnable()).start();

3. 实现 Callable 接口

java
1class MyCallable implements Callable<String> {
2 @Override
3 public String call() throws Exception {
4 return "Callable result";
5 }
6}
7
8FutureTask<String> task = new FutureTask<>(new MyCallable());
9new Thread(task).start();
10String result = task.get();

4. 使用线程池

java
1ExecutorService executor = Executors.newFixedThreadPool(5);
2executor.submit(() -> System.out.println("Task"));
3executor.shutdown();

5. 使用 CompletableFuture

java
1CompletableFuture.runAsync(() -> {
2 System.out.println("Async task");
3});

6. 使用 ForkJoinPool

java
1ForkJoinPool pool = new ForkJoinPool();
2pool.submit(() -> System.out.println("ForkJoin task"));

7. 使用 Timer

java
1Timer timer = new Timer();
2timer.schedule(new TimerTask() {
3 @Override
4 public void run() {
5 System.out.println("Timer task");
6 }
7}, 1000);

总结

本文档涵盖了 Java 并发编程的 62 个核心面试题,包括:

  • 线程基础:线程创建、生命周期、通信机制
  • 线程池:原理、参数配置、拒绝策略
  • 锁机制:synchronized、ReentrantLock、读写锁、AQS
  • 并发工具:CountDownLatch、CyclicBarrier、Semaphore
  • 并发容器:ConcurrentHashMap、阻塞队列
  • 原子类:AtomicInteger、LongAdder、CAS
  • 内存模型:JMM、happens-before、指令重排
  • ThreadLocal:原理、使用、最佳实践

掌握这些知识点,能够帮助你在面试中脱颖而出,在实际工作中编写高质量的并发代码。

forum

评论区 / Comments