Java并发编程高频面试题

一、基础概念

1. 并行与并发的区别?

  • 并行:多个任务在多个CPU核心上同时执行(物理上同时)。
  • 并发:多个任务在单CPU核心上交替执行(逻辑上同时)。
  • 类比:并行是多个窗口同时服务,并发是一个窗口轮流服务。

2. 线程的创建方式?

  • 继承 Thread :重写run()方法,通过start()启动。
  • 实现 Runnable接口:更灵活,避免单继承限制。
  • 实现 Callable接口:支持返回值和异常处理,配合FutureTask使用。

3. 线程的状态?

状态 说明
NEW 线程被创建但未启动
RUNNABLE 线程正在执行或等待CPU资源
BLOCKED 线程等待锁(进入同步代码块)
WAITING 线程调用wait()/join()后等待唤醒
TIMED_WAITING 超时等待(如sleep(long)
TERMINATED 线程执行完毕或异常终止

4. sleep() wait()的区别?

特性 sleep() wait()
所属类 Thread Object
锁行为 不释放锁 释放锁
唤醒方式 超时自动唤醒 需其他线程调用notify()唤醒
使用场景 线程休眠 线程间通信

5. 进程与线程的区别?

  • 进程:资源分配的最小单位(如内存、文件句柄)。
  • 线程:调度的最小单位,共享进程资源(如堆、方法区),但有独立栈和寄存器。
  • 协程:比线程更轻量级(如Kotlin的Coroutine)。

6.为什么用start()而非直接调用run()?

  • start()会创建新线程并执行run(),而直接调用run()仅在当前线程执行,无并发效果。

二、ThreadLocal

7. ThreadLocal的作用?

  • 线程隔离:为每个线程提供独立变量副本,避免共享数据冲突。
  • 典型场景:用户会话管理、数据库连接上下文传递。

8. ThreadLocal的实现原理?

  • 线程私有Map :每个线程维护一个ThreadLocal.ThreadLocalMap,键为ThreadLocal对象,值为线程变量。
  • 弱引用:键使用弱引用,防止内存泄漏。

9. ThreadLocal内存泄漏问题?

  • 原因 :线程未及时调用remove(),导致Entryvalue强引用无法被回收。
  • 解决 :使用try-finally确保调用remove()

三、Java内存模型(JMM)

9.JMM的核心是什么?

  • 定义线程间共享变量的访问规则,解决可见性、有序性、原子性问题。
  • 主内存:共享变量存储区。
  • 本地内存:线程私有的共享变量副本(抽象概念,对应CPU缓存等)。

10. JMM的三大特性?

  • 原子性 :操作不可分割(如synchronized保证代码块原子性)。
  • 可见性 :一个线程修改的值对其他线程立即可见(volatilesynchronized)。
  • 有序性 :禁止指令重排序(volatile通过内存屏障实现)。

11. volatile的作用?

  • 可见性:强制将修改刷新到主内存,禁止缓存。
  • 有序性:通过内存屏障禁止指令重排序。

12. volatile的作用及实现原理?

  • 作用:保证可见性和禁止指令重排。
  • 原理:
    • 写操作后插入Store-Barrier,强制刷新主内存。
    • 读操作前插入Load-Barrier,强制从主内存读取。

四、锁机制

13. synchronized的使用方式?

  • 修饰方法:锁对象实例(非静态方法)或类(静态方法)。
  • 修饰代码块:指定锁对象,更细粒度控制。

14. synchronized的实现原理?

  • Monitor对象:每个Java对象关联一个监视器,线程通过获取Monitor实现互斥。
  • 锁升级:偏向锁 → 轻量级锁 → 重量级锁(基于CAS和自旋优化)。

15. synchronized与ReentrantLock的区别?

特性 synchronized ReentrantLock
可重入 自动支持 需手动释放(unlock()
公平性 非公平 支持公平锁(通过构造函数)
锁获取 阻塞等待 支持tryLock()非阻塞获取
条件变量 wait()/notify() 支持Condition多条件变量

15. CAS的原理及问题?

  • 原理:Compare-And-Swap,通过原子指令实现无锁操作。
  • 问题
    • ABA问题 :通过版本号(如AtomicStampedReference)解决。
    • 循环开销:长时间自旋消耗CPU。

五、并发工具类

16. CountDownLatch与CyclicBarrier的区别?

  • CountDownLatch:计数器递减至0时释放所有等待线程(一次性)。
  • CyclicBarrier:所有线程到达屏障后继续执行(可重用)。

17. Semaphore的作用?

  • 控制并发量:允许指定数量的线程同时访问资源。
  • 应用场景:限流、资源池管理。

六、线程池

18. 线程池的工作流程?

  1. 任务提交到线程池。
  2. 核心线程处理任务,空闲则创建新线程(未达最大线程数)。
  3. 任务队列已满且线程数达最大值时,触发拒绝策略。

提交任务 → 核心线程执行 → 队列缓冲 → 最大线程处理 → 拒绝策略。

19. 线程池参数配置?

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:空闲线程存活时间。
  • workQueue :阻塞队列(如LinkedBlockingQueue)。
  • handler :拒绝策略(如AbortPolicy)。

20. 线程池的拒绝策略?

  • AbortPolicy:抛异常(默认)。
  • CallerRunsPolicy:任务回退到调用线程执行。
  • DiscardPolicy:静默丢弃。
  • DiscardOldestPolicy:丢弃队列中最老的任务。

七、高级主题

21. 死锁的条件及避免?

  • 条件:互斥、持有并等待、不可抢占、循环等待。
  • 避免
    • 按顺序加锁。
    • 设置超时时间。
    • 使用ReentrantLocktryLock()

22. ConcurrentHashMap的线程安全机制?

  • 分段锁(JDK7):将数据分段,锁粒度细化。
  • CAS+synchronized(JDK8):数组+链表/红黑树结构,CAS保证原子性,synchronized保证同步。

23.CAS的问题及解决?

  • 问题:ABA问题、循环开销大。
  • 解决:AtomicStampedReference解决ABA,结合volatile减少循环。

八、代码实战

24. 手写双重校验单例模式?

java 复制代码
public class Singleton {
    private static volatile Singleton instance; // 禁止指令重排

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) { // 加锁
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 初始化对象
                }
            }
        }
        return instance;
    }
}

volatile的必要性

  • 防止instance = new Singleton()的指令重排(先分配内存,再初始化对象,最后赋值引用)。
  • 若未使用volatile,其他线程可能在初始化完成前拿到未完全构造的对象。

25.线程的创建方式对比

java 复制代码
// 继承Thread类
public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running");
    }
}

// 实现Runnable接口
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable running");
    }
}

// 实现Callable接口
public class CallableDemo implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable result";
    }
}

// 使用示例
public static void main(String[] args) throws Exception {
    // Thread
    new ThreadDemo().start();
    
    // Runnable
    new Thread(new RunnableDemo()).start();
    
    // Callable + FutureTask
    FutureTask<String> futureTask = new FutureTask<>(new CallableDemo());
    new Thread(futureTask).start();
    System.out.println("Result: " + futureTask.get());
}

原理对比:

  • Thread类本身实现了Runnable接口,通过继承方式耦合度较高。
  • Runnable和Callable将任务与线程解耦,支持更灵活的扩展。
  • Callable通过FutureTask包装后,可通过get()方法获取异步结果。

26.ThreadLocal内存泄漏

java 复制代码
public class ThreadLocalDemo {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("value");
        // 未调用threadLocal.remove(),可能导致内存泄漏
    }
}

原理分析:

  • ThreadLocalMap的Entry以ThreadLocal为键(弱引用),若未手动remove(),当ThreadLocal对象被回收后,value仍被强引用在Entry中。
  • 解决方案
java 复制代码
try {
    threadLocal.set("value");
    // 业务逻辑
} finally {
    threadLocal.remove(); // 在finally中确保清理
}

27.volatile的内存屏障

java 复制代码
public class VolatileDemo {
    private volatile int x = 0;

    public void write() {
        x = 1; // 写操作后插入Store-Barrier
    }

    public void read() {
        int y = x; // 读操作前插入Load-Barrier
    }
}

内存屏障原理

  • Store-Barrier:确保屏障前的写操作全部刷新到主内存。
  • Load-Barrier:确保屏障后的读操作从主内存获取最新值。
  • 禁止指令重排:通过内存屏障阻止编译器和CPU对volatile变量操作的重排序。

28.synchronized vs ReentrantLock

java 复制代码
// synchronized示例
public class SynchronizedDemo {
    public synchronized void method() {
        // 同步代码
    }
}

// ReentrantLock示例
public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public void method() {
        lock.lock();
        try {
            // 同步代码
        } finally {
            lock.unlock();
        }
    }
}

29.CountDownLatch vs CyclicBarrier

java 复制代码
// CountDownLatch示例
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(() -> {
            System.out.println("Task1 done");
            latch.countDown();
        }).start();
        new Thread(() -> {
            System.out.println("Task2 done");
            latch.countDown();
        }).start();
        latch.await(); // 等待两个任务完成
        System.out.println("All tasks done");
    }
}

// CyclicBarrier示例
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(2, () -> {
            System.out.println("Barrier reached");
        });
        new Thread(() -> {
            try {
                System.out.println("Task1 ready");
                barrier.await();
                System.out.println("Task1 continue");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                System.out.println("Task2 ready");
                barrier.await();
                System.out.println("Task2 continue");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

核心区别

  • CountDownLatch:计数器递减到0后不可重置,用于"等待多个任务完成"。
  • CyclicBarrier:计数器达到阈值后重置,可循环使用,用于"多个线程同步执行"。

30.线程池工作流程

java 复制代码
ExecutorService executor = new ThreadPoolExecutor(
    2, // corePoolSize
    4, // maximumPoolSize
    30, // keepAliveTime
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10), // workQueue
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

工作流程

  1. 提交任务 → 核心线程执行(未达corePoolSize时创建新线程)。
  2. 核心线程满 → 任务存入队列(如ArrayBlockingQueue)。
  3. 队列满 → 非核心线程执行(不超过maximumPoolSize)。
  4. 所有线程忙且队列满 → 触发拒绝策略(如AbortPolicy抛异常)。

31.CAS的ABA问题

java 复制代码
AtomicInteger atomicInt = new AtomicInteger(100);
// 线程A:
int oldValue = atomicInt.get();
// 假设线程B将值改为101,再改回100
atomicInt.compareAndSet(oldValue, 200); // CAS成功,但值已被篡改

解决方案

java 复制代码
AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0);
// 线程A:
int[] stampHolder = new int[1];
int oldValue = stampedRef.get(stampHolder);
int oldStamp = stampHolder[0];
// 线程B修改值并增加版本号
stampedRef.compareAndSet(oldValue, 101, oldStamp, oldStamp + 1);
// 线程A再次尝试CAS:
stampedRef.compareAndSet(oldValue, 200, oldStamp, oldStamp + 1); // 失败(版本号不匹配)

总结 :以上题目覆盖Java并发编程核心知识点,建议重点掌握线程安全实现、锁优化、JMM原理、线程池调优等模块。面试时需结合源码和实际场景说明设计原理,体现对底层机制的理解。

相关推荐
卫崽12 分钟前
JavaScript 中的 ?? 与 || 运算符详解
javascript·面试
阿杆24 分钟前
🤯我写了一套无敌的参数校验组件④ | 现已支持 i18n
java·spring
逆袭的小黄鸭24 分钟前
JavaScript 开发必备规范:命名、语法与代码结构指南
前端·javascript·面试
小样vvv24 分钟前
【微服务管理】注册中心:分布式系统的基石
java·数据库·微服务
amagi60028 分钟前
Java中的正则表达式(Regular Expression)
java
独立开阀者_FwtCoder34 分钟前
2025年,真心佩服的十大开源工具
前端·后端·面试
喵手34 分钟前
如何快速掌握 Java 反射之获取类的字段?
java·后端·java ee
AronTing37 分钟前
06- 服务网格实战:从 Istio 核心原理到微服务治理升级
java·后端·架构
奋进的小暄37 分钟前
贪心算法(18)(java)距离相等的条形码
java·开发语言·贪心算法
雷渊38 分钟前
Elasticsearch查询为什么这么快
java