一篇讲清楚 Java 并发编程
·
一篇讲清楚 Java 并发编程
前言
并发编程是 Java 开发中不可或缺的技能,也是面试中的高频考点。本文将系统性地讲解 Java 并发编程的核心知识体系,帮助你建立完整的并发编程知识框架。
一、并发基础概念
1.1 进程 vs 线程
| 特性 | 进程 | 线程 |
|---|---|---|
| 定义 | 操作系统资源分配的基本单位 | CPU 调度和执行的基本单位 |
| 资源占用 | 独立内存空间 | 共享进程资源 |
| 通信 | IPC(管道、消息队列等) | 共享内存、通信简单 |
| 切换开销 | 大 | 小 |
1.2 并发 vs 并行
- 并发:同一时间段内,多个任务都在进行(时间片轮转)
- 并行:同一时刻,多个任务同时进行(多核 CPU)
1.3 线程状态
public enum State {
NEW, // 新建状态
RUNNABLE, // 可运行状态
BLOCKED, // 阻塞状态
WAITING, // 等待状态
TIMED_WAITING, // 超时等待状态
TERMINATED // 终止状态
}
二、线程的创建与使用
2.1 创建线程的四种方式
方式一:继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中");
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
方式二:实现 Runnable 接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行中");
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
方式三:实现 Callable 接口(有返回值)
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "线程执行结果";
}
}
// 使用
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 阻塞获取结果
方式四:使用线程池(推荐)
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
System.out.println("线程池执行任务");
});
executor.shutdown();
2.2 线程的优先级
thread.setPriority(Thread.MAX_PRIORITY); // 10
thread.setPriority(Thread.MIN_PRIORITY); // 1
thread.setPriority(Thread.NORM_PRIORITY); // 5(默认)
三、Synchronized 关键字
3.1 三种使用方式
实例方法锁
public class Counter {
private int count = 0;
// 锁住的是 this 对象
public synchronized void increment() {
count++;
}
}
静态方法锁
public class Counter {
private static int count = 0;
// 锁住的是 Class 对象
public static synchronized void increment() {
count++;
}
}
代码块锁
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 锁住指定的对象
count++;
}
}
}
3.2 Synchronized 原理
- Monitor 监视器:每个对象都有一个 Monitor
- 字节码层面:
monitorenter和monitorexit - 锁升级过程:偏向锁 → 轻量级锁 → 重量级锁
四、Volatile 关键字
4.1 作用
public class VolatileExample {
// 保证可见性和有序性,但不保证原子性
private volatile boolean flag = false;
public void setFlag() {
flag = true; // 立即对其他线程可见
}
public void checkFlag() {
while (!flag) {
// 等待 flag 变为 true
}
}
}
4.2 Volatile vs Synchronized
| 特性 | Volatile | Synchronized |
|---|---|---|
| 可见性 | ✓ | ✓ |
| 有序性 | ✓ | ✓ |
| 原子性 | ✗ | ✓ |
| 阻塞 | ✗ | ✓ |
五、CAS 与 AQS
5.1 CAS(Compare And Swap)
// CAS 原理示例(伪代码)
public boolean compareAndSet(int expect, int update) {
if (value == expect) {
value = update;
return true;
}
return false;
}
ABA 问题:解决方式是使用版本号 AtomicStampedReference
5.2 AQS(AbstractQueuedSynchronizer)
AQS 是 Java 并发包的基石,核心思想:
- 状态变量:
volatile int state - CLH 队列:等待线程的队列
- 独占/共享:两种模式
// ReentrantLock 基于 AQS 实现
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
六、并发工具类
6.1 CountDownLatch(倒计时门闩)
CountDownLatch latch = new CountDownLatch(3);
// 启动 3 个线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行任务
} finally {
latch.countDown(); // 计数减 1
}
}).start();
}
latch.await(); // 等待计数器归零
System.out.println("所有任务完成");
6.2 CyclicBarrier(循环栅栏)
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障点");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行任务
barrier.await(); // 等待其他线程
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
6.3 Semaphore(信号量)
Semaphore semaphore = new Semaphore(5); // 允许 5 个线程同时访问
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
// 访问共享资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
6.4 Exchanger(交换器)
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data = "数据A";
String received = exchanger.exchange(data);
System.out.println("线程A收到: " + received);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
String data = "数据B";
String received = exchanger.exchange(data);
System.out.println("线程B收到: " + received);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
七、线程池
7.1 为什么使用线程池
- 降低资源消耗:重复利用已创建的线程
- 提高响应速度:任务到达时直接执行
- 便于管理:统一分配、调优和监控
7.2 线程池核心参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize: 核心线程数
10, // maximumPoolSize: 最大线程数
60L, // keepAliveTime: 非核心线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
7.3 四种拒绝策略
| 策略 | 说明 |
|---|---|
| AbortPolicy(默认) | 抛出异常 |
| CallerRunsPolicy | 调用者所在线程执行 |
| DiscardPolicy | 直接丢弃 |
| DiscardOldestPolicy | 丢弃队列最前面的任务 |
7.4 合理配置线程池
// CPU 密集型
int n = Runtime.getRuntime().availableProcessors();
poolSize = n + 1
// IO 密集型
poolSize = n * 2
7.5 禁止使用 Executors 创建
// 不推荐(OOM 风险)
ExecutorService executor1 = Executors.newFixedThreadPool(10);
ExecutorService executor2 = Executors.newCachedThreadPool();
// 推荐:明确参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
八、锁框架
8.1 ReentrantLock vs Synchronized
| 特性 | ReentrantLock | Synchronized |
|---|---|---|
| 可中断 | ✓ | ✗ |
| 公平锁 | ✓ | ✗(非公平) |
| 超时获取 | ✓ | ✗ |
| 条件变量 | 支持多个 | 单一 |
| 自动释放 | 手动 | 自动 |
8.2 ReadWriteLock(读写锁)
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁(共享锁)
rwLock.readLock().lock();
try {
// 读操作
} finally {
rwLock.readLock().unlock();
}
// 写锁(排他锁)
rwLock.writeLock().lock();
try {
// 写操作
} finally {
rwLock.writeLock().unlock();
}
8.3 StampedLock(印章锁)
StampedLock lock = new StampedLock();
// 乐观读
long stamp = lock.tryOptimisticRead();
// 读取数据
if (!lock.validate(stamp)) {
// 验证失败,升级为悲观读
stamp = lock.readLock();
try {
// 重新读取
} finally {
lock.unlockRead(stamp);
}
}
九、并发容器
9.1 ConcurrentHashMap
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");
// Java 8+ 原子操作
map.computeIfAbsent("key", k -> "default");
map.merge("key", "value", (oldVal, newVal) -> oldVal + newVal);
9.2 CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item"); // 写时复制
// 适合读多写少的场景
for (String item : list) {
// 读取不需要加锁
}
9.3 BlockingQueue
// ArrayBlockingQueue: 有界队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// LinkedBlockingQueue: 可选有界
BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(100);
// PriorityBlockingQueue: 优先级队列
BlockingQueue<Integer> queue3 = new PriorityBlockingQueue<>();
// SynchronousQueue: 同步队列(不存储元素)
BlockingQueue<String> queue4 = new SynchronousQueue<>();
十、原子类
10.1 基本类型原子类
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet(); // ++i
atomicInt.getAndIncrement(); // i++
atomicInt.addAndGet(5); // i += 5
atomicInt.compareAndSet(5, 10); // CAS
10.2 引用类型原子类
AtomicReference<User> userRef = new AtomicReference<>();
User user = new User("Alice");
userRef.set(user);
userRef.compareAndSet(user, newUser);
10.3 数组类型原子类
AtomicIntegerArray array = new AtomicIntegerArray(10);
array.getAndSet(0, 100);
array.incrementAndGet(1);
10.4 对象属性修改原子类
AtomicIntegerFieldUpdater<User> updater =
AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User();
updater.getAndIncrement(user);
10.5 原子累加器(Java 8+)
LongAdder adder = new LongAdder();
adder.increment(); // 性能优于 AtomicLong
LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
accumulator.accumulate(10);
十一、常见问题与解决方案
11.1 死锁
产生原因:多个线程互相持有对方需要的资源
避免方法:
- 固定加锁顺序
- 使用
tryLock()设置超时 - 使用死锁检测机制
// 超时获取锁
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 执行业务
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
11.2 活锁
现象:线程不断改变状态,但无法继续执行
解决:引入随机性或重试机制
11.3 线程安全问题排查
- 查看线程堆栈:
jstack <pid> - 查看内存状态:
jmap -histo <pid> - 使用工具:VisualVM、Arthas
十二、最佳实践
12.1 编码建议
- 同步块范围最小化:减少锁持有时间
- 使用线程局部变量:
ThreadLocal - 避免使用 stop/suspend:使用 interrupt
- 及时释放锁:使用 try-finally
12.2 性能优化
- 减小锁粒度:分段锁(ConcurrentHashMap 思想)
- 读写分离:ReadWriteLock
- 无锁编程:CAS、原子类
- 协程:Java 21+ 虚拟线程
12.3 Java 21+ 虚拟线程
// Java 21+
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 虚拟线程执行任务
});
}
总结
Java 并发编程是一个庞大的知识体系,本文涵盖了从基础概念到高级特性的核心内容。学习并发编程的关键是:
- 理解基础:掌握线程、锁、内存模型等核心概念
- 多实践:通过代码加深理解
- 看源码:学习 JDK 并发包的实现原理
- 关注更新:Java 虚拟线程等新特性
希望这篇文章能帮助你系统地掌握 Java 并发编程!
参考资料
- 《Java 并发编程的艺术》
- 《深入理解 Java 虚拟机》
- Java 并发包源码(JDK)
更多推荐

所有评论(0)