1. Java多线程基础概念与核心机制
多线程编程是现代Java开发中不可或缺的核心技能。理解线程的基本概念是掌握高级并发工具的前提。每个线程都拥有独立的程序计数器、虚拟机栈和本地方法栈,但共享堆内存和方法区。这种结构既保证了线程隔离,又实现了数据共享。
线程生命周期包含新建(NEW)、就绪(RUNNABLE)、运行(RUNNING)、阻塞(BLOCKED)和终止(TERMINATED)五种状态。面试中常被问及的是wait()与sleep()的区别:
- wait()是Object方法,会释放锁,需在同步块中使用
- sleep()是Thread方法,不释放锁,可在任何地方调用
- wait()通常用于线程间通信,sleep()用于暂停执行
关键提示:在Java中实现多线程有三种方式:继承Thread类、实现Runnable接口、使用Callable+Future。但本质上都是通过创建Thread对象来启动线程,区别在于Runnable和Callable更适合资源分离的场景。
2. synchronized关键字深度解析
作为Java内置的同步机制,synchronized的底层实现经历了多次优化。在JDK1.6之前它是重量级锁,之后引入了偏向锁、轻量级锁等优化策略。
2.1 使用方式与内存语义
synchronized有三种应用方式:
java复制// 实例方法同步
public synchronized void method1() { /*...*/ }
// 静态方法同步
public static synchronized void method2() { /*...*/ }
// 同步代码块
public void method3() {
synchronized(this) { /*...*/ }
synchronized(MyClass.class) { /*...*/ }
}
每种方式对应的锁对象不同:
- 实例方法锁定当前实例(this)
- 静态方法锁定Class对象
- 同步块锁定指定对象
2.2 锁升级过程详解
现代JVM中synchronized的锁状态变化路径:
- 无锁状态:对象刚创建时的初始状态
- 偏向锁:当第一个线程访问时,通过CAS将Mark Word中的线程ID设为当前线程
- 轻量级锁:当出现竞争时,撤销偏向锁,转为基于CAS的自旋锁
- 重量级锁:自旋超过阈值(默认10次)后升级为操作系统级别的互斥锁
注意事项:从JDK15开始,偏向锁已被标记为废弃,JDK21中相关参数也被移除。现代JVM的锁升级路径简化为:无锁→轻量级锁→重量级锁。
3. JUC核心组件解析
java.util.concurrent包提供了丰富的并发工具类,其核心是AbstractQueuedSynchronizer(AQS)。
3.1 AQS工作原理
AQS通过一个volatile的int状态变量和CLH队列实现同步控制。其核心方法包括:
- acquire():获取资源
- release():释放资源
- tryAcquire():尝试获取(需子类实现)
- tryRelease():尝试释放(需子类实现)
状态变量的含义由具体实现决定:
- ReentrantLock:表示重入次数
- Semaphore:表示剩余许可数
- CountDownLatch:表示剩余计数
3.2 可重入锁ReentrantLock
与synchronized相比,ReentrantLock提供了更灵活的锁控制:
java复制ReentrantLock lock = new ReentrantLock(true); // 公平锁
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
优势特性:
- 可中断的锁获取
- 超时获取锁
- 公平/非公平模式选择
- 多个条件变量支持
4. 并发工具类实战应用
4.1 CountDownLatch使用场景
适用于主线程等待多个子任务完成的场景:
java复制CountDownLatch latch = new CountDownLatch(3);
// 工作线程
new Thread(() -> {
doWork();
latch.countDown();
}).start();
// 主线程
latch.await();
System.out.println("所有任务完成");
4.2 CyclicBarrier与CountDownLatch对比
两者主要区别:
- CountDownLatch不可重置,CyclicBarrier可重复使用
- CountDownLatch主线程等待,CyclicBarrier所有线程相互等待
典型CyclicBarrier用法:
java复制CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("所有线程到达屏障点"));
IntStream.range(0, 3).forEach(i -> new Thread(() -> {
doWork();
barrier.await(); // 等待其他线程
}).start());
4.3 Semaphore流量控制
控制同时访问特定资源的线程数量:
java复制Semaphore semaphore = new Semaphore(5); // 允许5个并发
void accessResource() throws InterruptedException {
semaphore.acquire();
try {
// 使用受限资源
} finally {
semaphore.release();
}
}
5. 线程安全集合类
5.1 ConcurrentHashMap演进
| JDK版本 | 实现方式 | 特点 |
|---|---|---|
| 1.7 | 分段锁(Segment) | 锁粒度较粗 |
| 1.8+ | CAS+synchronized | 锁粒度细化到桶节点 |
关键优化点:
- 当链表长度>8时转为红黑树
- 扩容时支持多线程协助迁移
- 使用@Contended避免伪共享
5.2 CopyOnWrite容器
适用于读多写少的场景,通过写时复制保证线程安全:
java复制CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 读操作无锁
list.get(0);
// 写操作复制新数组
list.add("new element");
6. 原子变量与CAS原理
6.1 原子类家族
- 基本类型:AtomicInteger, AtomicLong, AtomicBoolean
- 引用类型:AtomicReference, AtomicStampedReference
- 数组类型:AtomicIntegerArray, AtomicReferenceArray
- 字段更新:AtomicIntegerFieldUpdater
6.2 CAS底层实现
CAS(Compare-And-Swap)是CPU提供的原子指令,在x86架构对应cmpxchg指令。Java通过Unsafe类提供CAS支持:
java复制public final class AtomicInteger {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
CAS的典型问题:
- ABA问题(通过版本号解决)
- 自旋CPU开销
- 只能保证单个变量原子性
7. 线程池深度解析
7.1 核心参数配置
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
unit, // 时间单位
workQueue, // 工作队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
7.2 四种拒绝策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy | 抛出RejectedExecutionException | 需要明确知道任务被拒绝 |
| CallerRunsPolicy | 由调用线程执行该任务 | 不允许任务丢失的场景 |
| DiscardPolicy | 静默丢弃任务 | 可容忍任务丢失 |
| DiscardOldestPolicy | 丢弃队列最老任务 | 优先处理新任务 |
8. 面试高频问题解析
8.1 synchronized vs ReentrantLock
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM层面 | JDK代码实现 |
| 锁获取 | 自动获取释放 | 需显式调用lock/unlock |
| 可中断 | 不支持 | 支持 |
| 公平锁 | 非公平 | 可配置 |
| 条件变量 | 单个 | 多个 |
| 性能 | 优化后相当 | 优化后相当 |
8.2 volatile关键字
volatile保证可见性和有序性,但不保证原子性。其底层通过内存屏障实现:
- 写操作:StoreStore屏障 + volatile写 + StoreLoad屏障
- 读操作:volatile读 + LoadLoad屏障 + LoadStore屏障
典型应用场景:
- 状态标志位
- 单例模式双重检查锁定
- 线程间简单状态通信
9. 并发编程实践建议
- 优先使用高层并发工具(如ConcurrentHashMap)
- 尽量减小同步代码块范围
- 避免在持有锁时调用外部方法
- 使用线程池而非直接创建线程
- 考虑使用不可变对象
- 合理设置线程池参数
- 使用ThreadLocal避免共享变量
- 优先使用并发集合而非同步包装器
经验之谈:在最近的项目中,我们通过将HashMap替换为ConcurrentHashMap,配合适当的锁分段策略,使系统吞吐量提升了3倍。关键是要根据具体场景选择合适的并发控制粒度。
