Java并发编程高频面试题大全(2024最新版)

Java并发编程高频面试题大全(2024最新版)

Java并发编程是面试中的重头戏,几乎每场Java面试都会涉及。本文精心整理了最常见、最高频的并发面试题,覆盖从基础到进阶的各个层面,帮助大家系统复习,从容应对面试。


一、线程基础

1. 线程和进程的区别是什么?

| 对比项 | 进程 | 线程 | |--------|------|------| | 定义 | 程序的一次执行过程 | 进程内的一个执行单元 | | 资源 | 拥有独立的内存空间 | 共享进程的资源 | | 开销 | 创建和切换开销大 | 创建和切换开销小 | | 通信 | 需要IPC机制(管道、消息队列等) | 可以直接共享内存通信 | | 影响 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |

2. 创建线程有哪几种方式?

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable 接口(配合 FutureTask,可以有返回值)
  4. 使用线程池(Executor 框架)
// 方式1:继承Thread类
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread方式");
    }
}

// 方式2:实现Runnable接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable方式");
    }
}

// 方式3:实现Callable接口
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable方式";
    }
}

// 方式4:线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(() -> System.out.println("线程池方式"));

3. 线程的生命周期(状态)有哪些?

Java线程有 6种状态Thread.State枚举):

  1. NEW:新建,线程对象已创建但未调用start()
  2. RUNNABLE:可运行,包括就绪(等待CPU)和运行中
  3. BLOCKED:阻塞,等待获取监视器锁
  4. WAITING:等待,调用wait()/join()/LockSupport.park()等
  5. TIMED_WAITING:超时等待,调用sleep(ms)/wait(ms)/join(ms)等
  6. TERMINATED:终止,线程执行完毕或异常退出

4. start()和run()的区别?

  • 调用 start() 会创建一个新的线程,并让该线程执行run()方法中的代码
  • 直接调用 run() 不会创建新线程,只是在当前线程中执行run()方法体
  • 一个线程只能调用一次start(),重复调用会抛出IllegalThreadStateException

二、synchronized 关键字

5. synchronized 的使用方式和原理?

三种使用方式:

  1. 修饰实例方法:锁的是当前实例对象
  2. 修饰静态方法:锁的是当前类的Class对象
  3. 修饰代码块:锁的是指定对象

底层原理:

  • 同步代码块:通过 monitorentermonitorexit 指令实现
  • 同步方法:通过方法访问标志 ACC_SYNCHRONIZED 实现
  • 本质都是基于 Monitor(监视器锁) 实现

6. synchronized 锁升级过程?

JDK 1.6 引入了锁优化机制,锁的升级过程:

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

| 锁类型 | 适用场景 | 特点 | |--------|----------|------| | 偏向锁 | 只有一个线程访问同步块 | 在对象头中记录线程ID,无需CAS | | 轻量级锁 | 交替执行同步块 | 通过CAS竞争,失败则自旋 | | 重量级锁 | 竞争激烈 | 阻塞等待,涉及用户态和内核态切换 |

注意:锁升级是单向的,不可降级(GC时可降级)。


三、volatile 关键字

7. volatile 的作用和原理?

两大作用:

  1. 保证可见性:一个线程修改了volatile变量,其他线程能立即看到最新值
  2. 禁止指令重排序:通过内存屏障(Memory Barrier)实现

底层原理:

  • 写操作:在写指令后插入 StoreLoad 屏障
  • 读操作:在读指令前插入 LoadLoad 屏障
  • 底层使用 lock前缀指令,将当前CPU缓存行的数据写回主内存,并使其他CPU中该变量的缓存行失效

8. volatile 能保证线程安全吗?

不能完全保证! volatile 只保证了可见性和有序性,不保证原子性

// 经典反例:volatile不能保证i++的原子性
public class Counter {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // 非原子操作:读取→加1→写回
    }
}

i++ 实际上是三步操作(读→改→写),需要使用 AtomicIntegersynchronized 来保证原子性。


四、CAS 与原子类

9. 什么是 CAS?有什么问题?

CAS(Compare And Swap):比较并交换,是一种无锁的并发控制方式。

// CAS 伪代码
boolean cas(V, expectedValue, newValue) {
    if (V == expectedValue) {
        V = newValue;
        return true;
    }
    return false;
}

CAS 存在的问题:

  1. ABA问题:值从A→B→A,CAS认为没有变化。解决:使用 AtomicStampedReference(带版本号)
  2. 自旋开销:CAS长时间不成功会一直循环,消耗CPU
  3. 只能保证一个共享变量的原子操作:解决:使用 AtomicReference 将多个变量封装成一个对象

10. 常用的原子类有哪些?

| 类名 | 说明 | |------|------| | AtomicInteger | 整型原子类 | | AtomicLong | 长整型原子类 | | AtomicBoolean | 布尔型原子类 | | AtomicReference | 引用类型原子类 | | AtomicStampedReference | 带版本号的引用类型(解决ABA问题) | | LongAdder | JDK8新增,高并发下性能优于AtomicLong |


五、线程池

11. 为什么要使用线程池?

  1. 降低资源消耗:重复利用已创建的线程,减少线程创建和销毁的开销
  2. 提高响应速度:任务到达时无需等待线程创建即可执行
  3. 提高可管理性:统一管理线程,避免无限制创建线程导致OOM

12. ThreadPoolExecutor 的核心参数有哪些?

public ThreadPoolExecutor(
    int corePoolSize,        // 核心线程数
    int maximumPoolSize,     // 最大线程数
    long keepAliveTime,      // 非核心线程空闲存活时间
    TimeUnit unit,           // 时间单位
    BlockingQueue<Runnable> workQueue,  // 阻塞队列
    ThreadFactory threadFactory,        // 线程工厂
    RejectedExecutionHandler handler    // 拒绝策略
)

执行流程:

提交任务 → 核心线程未满?→ 创建核心线程执行
         → 核心线程满,队列未满?→ 放入队列等待
         → 队列满,未达最大线程数?→ 创建非核心线程执行
         → 达到最大线程数且队列满?→ 执行拒绝策略

13. 四种拒绝策略是什么?

| 拒绝策略 | 说明 | |----------|------| | AbortPolicy(默认) | 抛出RejectedExecutionException异常 | | CallerRunsPolicy | 由提交任务的线程自己执行该任务 | | DiscardPolicy | 直接丢弃任务,不抛异常 | | DiscardOldestPolicy | 丢弃队列中最老的任务,重新提交新任务 |

14. 为什么不推荐使用 Executors 创建线程池?

// 不推荐!
Executors.newFixedThreadPool();     // 队列是Integer.MAX_VALUE,可能OOM
Executors.newSingleThreadExecutor(); // 队列是Integer.MAX_VALUE,可能OOM
Executors.newCachedThreadPool();     // 最大线程数Integer.MAX_VALUE,可能OOM

阿里Java开发手册明确规定:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,避免资源耗尽风险。


六、AQS 与 Lock

15. 什么是 AQS?

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

核心思想:

  • 使用一个 volatile int state 变量表示同步状态
  • 通过 CLH队列(FIFO双向链表)管理等待获取锁的线程
  • 提供 模板方法,子类重写 tryAcquire()/tryRelease() 等实现不同语义

基于AQS实现的同步器:

  • ReentrantLock
  • ReentrantReadWriteLock
  • CountDownLatch
  • Semaphore
  • CyclicBarrier

16. synchronized 和 ReentrantLock 的区别?

| 对比项 | synchronized | ReentrantLock | |--------|-------------|---------------| | 实现方式 | JVM层面(关键字) | API层面(类) | | 锁的获取 | 自动获取 | 手动lock() | | 锁的释放 | 自动释放 | 手动unlock()(必须在finally中释放) | | 可中断 | 不可中断 | 可中断(lockInterruptibly()) | | 公平性 | 非公平锁 | 可选公平/非公平 | | 条件变量 | 只有一个wait/notify | 支持多个Condition | | 可重入 | 是 | 是 |


七、并发工具类

17. CountDownLatch 和 CyclicBarrier 的区别?

| 对比项 | CountDownLatch | CyclicBarrier | |--------|---------------|---------------| | 计数方式 | 递减计数 | 递减到0后可重置 | | 触发方式 | 一个线程等待其他线程完成 | 多个线程互相等待 | | 可重用 | 不可重用(一次性) | 可重用(自动重置) | | 使用场景 | 主线程等待子任务完成 | 多线程同时开始执行 |

// CountDownLatch 示例:主线程等待3个子线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行任务
        latch.countDown();
    }).start();
}
latch.await(); // 等待所有子线程完成
System.out.println("所有任务完成");

18. Semaphore 是什么?

Semaphore(信号量) 用于控制同时访问特定资源的线程数量,可以用来实现限流。

// 只允许3个线程同时访问
Semaphore semaphore = new Semaphore(3);
semaphore.acquire(); // 获取许可
try {
    // 访问资源
} finally {
    semaphore.release(); // 释放许可
}

八、ThreadLocal

19. ThreadLocal 的原理是什么?会有什么问题?

原理:

  • 每个Thread对象内部有一个 ThreadLocalMap 成员变量
  • ThreadLocal的set/get操作实际上是在当前线程的ThreadLocalMap中存取数据
  • key是ThreadLocal对象的弱引用,value是存储的值

内存泄漏问题:

  • ThreadLocalMap 的 key 是弱引用,GC时key可能被回收,但value是强引用
  • 导致 key 为 null 的 Entry,value 无法被回收
  • 解决方法: 使用完后务必调用 threadLocal.remove() 清除数据

九、线程间通信

20. 线程间通信有哪些方式?

  1. volatile:通过共享内存实现可见性
  2. wait/notify:Object类的方法,需要配合synchronized使用
  3. Lock/Condition:更灵活的等待/通知机制
  4. CountDownLatch/CyclicBarrier:并发工具类
  5. BlockingQueue:阻塞队列
  6. 管道流:PipedOutputStream / PipedInputStream

十、实战场景题

21. 如何实现一个生产者-消费者模型?

// 使用 BlockingQueue 实现(最简洁)
public class ProducerConsumer {
    private BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

    public void produce() throws InterruptedException {
        queue.put("product");  // 队列满时阻塞
    }

    public void consume() throws InterruptedException {
        queue.take();  // 队列空时阻塞
    }
}

22. 如何控制三个线程按顺序执行?

// 方式1:使用join()
Thread t1 = new Thread(() -> System.out.println("T1"));
Thread t2 = new Thread(() -> { t1.join(); System.out.println("T2"); });
Thread t3 = new Thread(() -> { t2.join(); System.out.println("T3"); });
t1.start(); t2.start(); t3.start();

// 方式2:使用CountDownLatch
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
new Thread(() -> { 
    System.out.println("T1"); 
    latch1.countDown(); 
}).start();
new Thread(() -> { 
    latch1.await(); 
    System.out.println("T2"); 
    latch2.countDown(); 
}).start();
new Thread(() -> { 
    latch2.await(); 
    System.out.println("T3"); 
}).start();

总结

Java并发编程的知识点非常多,面试中最核心的考察方向是:

  1. 线程基础:创建方式、生命周期
  2. 锁机制:synchronized锁升级、ReentrantLock、AQS原理
  3. volatile:可见性、有序性、不保证原子性
  4. CAS:原理与ABA问题
  5. 线程池:核心参数、执行流程、拒绝策略
  6. 并发工具:CountDownLatch、CyclicBarrier、Semaphore
  7. ThreadLocal:原理与内存泄漏

建议大家在理解原理的基础上,多动手写代码验证,这样才能在面试中应对自如!

💡 如果觉得本文有帮助,欢迎点赞收藏,祝你面试顺利!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值