一、基础概念
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()
,导致Entry
的value
强引用无法被回收。 - 解决:使用
try-finally
确保调用remove()
。
三、Java内存模型(JMM)
9.JMM的核心是什么?
- 定义线程间共享变量的访问规则,解决可见性、有序性、原子性问题。
- 主内存:共享变量存储区。
- 本地内存:线程私有的共享变量副本(抽象概念,对应CPU缓存等)。
10. JMM的三大特性?
- 原子性:操作不可分割(如
synchronized
保证代码块原子性)。 - 可见性:一个线程修改的值对其他线程立即可见(
volatile
或synchronized
)。 - 有序性:禁止指令重排序(
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。
- ABA问题:通过版本号(如
五、并发工具类
16. CountDownLatch与CyclicBarrier的区别?
- CountDownLatch:计数器递减至0时释放所有等待线程(一次性)。
- CyclicBarrier:所有线程到达屏障后继续执行(可重用)。
17. Semaphore的作用?
- 控制并发量:允许指定数量的线程同时访问资源。
- 应用场景:限流、资源池管理。
六、线程池
18. 线程池的工作流程?
- 任务提交到线程池。
- 核心线程处理任务,空闲则创建新线程(未达最大线程数)。
- 任务队列已满且线程数达最大值时,触发拒绝策略。
提交任务 → 核心线程执行 → 队列缓冲 → 最大线程处理 → 拒绝策略。
19. 线程池参数配置?
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:空闲线程存活时间。
- workQueue:阻塞队列(如
LinkedBlockingQueue
)。 - handler:拒绝策略(如
AbortPolicy
)。
20. 线程池的拒绝策略?
- AbortPolicy:抛异常(默认)。
- CallerRunsPolicy:任务回退到调用线程执行。
- DiscardPolicy:静默丢弃。
- DiscardOldestPolicy:丢弃队列中最老的任务。
七、高级主题
21. 死锁的条件及避免?
- 条件:互斥、持有并等待、不可抢占、循环等待。
- 避免:
- 按顺序加锁。
- 设置超时时间。
- 使用
ReentrantLock
的tryLock()
。
22. ConcurrentHashMap的线程安全机制?
- 分段锁(JDK7):将数据分段,锁粒度细化。
- CAS+synchronized(JDK8):数组+链表/红黑树结构,CAS保证原子性,synchronized保证同步。
23.CAS的问题及解决?
- 问题:ABA问题、循环开销大。
- 解决:AtomicStampedReference解决ABA,结合volatile减少循环。
八、代码实战
24. 手写双重校验单例模式?
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.线程的创建方式对比
// 继承Thread类
public class ThreadDemo extends Thread {@Overridepublic void run() {System.out.println("Thread running");}
}// 实现Runnable接口
public class RunnableDemo implements Runnable {@Overridepublic void run() {System.out.println("Runnable running");}
}// 实现Callable接口
public class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "Callable result";}
}// 使用示例
public static void main(String[] args) throws Exception {// Threadnew ThreadDemo().start();// Runnablenew Thread(new RunnableDemo()).start();// Callable + FutureTaskFutureTask<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内存泄漏
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中。
- 解决方案:
try {threadLocal.set("value");// 业务逻辑
} finally {threadLocal.remove(); // 在finally中确保清理
}
27.volatile的内存屏障
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
// 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
// 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.线程池工作流程
ExecutorService executor = new ThreadPoolExecutor(2, // corePoolSize4, // maximumPoolSize30, // keepAliveTimeTimeUnit.SECONDS,new ArrayBlockingQueue<>(10), // workQueueExecutors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
工作流程:
- 提交任务 → 核心线程执行(未达corePoolSize时创建新线程)。
- 核心线程满 → 任务存入队列(如ArrayBlockingQueue)。
- 队列满 → 非核心线程执行(不超过maximumPoolSize)。
- 所有线程忙且队列满 → 触发拒绝策略(如AbortPolicy抛异常)。
31.CAS的ABA问题
AtomicInteger atomicInt = new AtomicInteger(100);
// 线程A:
int oldValue = atomicInt.get();
// 假设线程B将值改为101,再改回100
atomicInt.compareAndSet(oldValue, 200); // CAS成功,但值已被篡改
解决方案:
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原理、线程池调优等模块。面试时需结合源码和实际场景说明设计原理,体现对底层机制的理解。