1. Java并发编程的核心挑战与价值
在服务器CPU核心数普遍达到32核甚至64核的今天,Java并发编程能力已成为区分初级与高级开发者的分水岭。我曾在电商大促期间亲眼见证,一个未妥善处理的并发bug导致库存超卖损失数百万。并发编程的真正难点不在于语法本身,而在于如何驾驭线程间微妙的竞态关系。
Java内存模型(JMM)规定了所有变量都存储在主内存中,每个线程有自己的工作内存。这种设计带来了可见性问题:线程A修改的变量,线程B可能永远看不到。举个例子:
java复制// 典型的内存可见性问题示例
public class VisibilityIssue {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while(flag) {} // 可能永远循环
System.out.println("Thread stopped");
}).start();
Thread.sleep(1000);
flag = false; // 主线程修改
}
}
这段代码在有些JVM实现中,子线程会陷入死循环,因为flag的修改对子线程不可见。解决方法是使用volatile关键字,它保证:
- 可见性:写操作立即刷新到主存
- 禁止指令重排序
2. 线程安全的三层防御体系
2.1 无状态设计
最简单的线程安全策略。比如下面这个统计服务:
java复制// 无状态服务示例
public class StatelessService {
public int process(String text) {
return text.length(); // 不依赖任何成员变量
}
}
2.2 不可变对象
Java中的String就是典型例子。实现不可变对象需要:
- 所有字段final
- 私有化所有字段
- 不提供setter方法
- 返回深拷贝而非原始引用
java复制public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 仅提供getter
}
2.3 同步控制
当必须使用可变状态时,同步是最后防线。Java提供了多种同步工具:
2.3.1 synchronized关键字
java复制public class Counter {
private int count;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步块
public void add(int value) {
synchronized(this) {
count += value;
}
}
}
重要经验:同步代码块应尽量小,但要注意原子性边界。我曾见过在方法级别滥用synchronized导致性能下降90%的案例。
2.3.2 Lock接口
相比synchronized,Lock提供了更灵活的控制:
java复制public class ReentrantLockDemo {
private final Lock lock = new ReentrantLock();
private int count;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须放在finally块
}
}
}
3. JUC包的核心武器库
3.1 ConcurrentHashMap的并发奥秘
- JDK7采用分段锁,默认16个段
- JDK8改为CAS+synchronized,锁粒度更细
java复制ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> {
// 线程安全的计算
return expensiveCalculation(k);
});
3.2 阻塞队列的四种策略
| 策略 | 抛出异常 | 返回特殊值 | 阻塞 | 超时 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e,t,u) |
| 移除 | remove() | poll() | take() | poll(t,u) |
| 检查 | element() | peek() | 不支持 | 不支持 |
3.3 线程池的七个核心参数
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 空闲线程存活时间(秒)
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 工作队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
四种拒绝策略对比:
- AbortPolicy:直接抛出RejectedExecutionException
- CallerRunsPolicy:由调用线程执行任务
- DiscardPolicy:静默丢弃
- DiscardOldestPolicy:丢弃队列最老任务
4. 原子类与CAS的魔法
4.1 AtomicInteger实现原理
java复制public class AtomicInteger extends Number {
private volatile int value;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
CAS(Compare-And-Swap)是CPU级别的原子指令,典型ABA问题可通过AtomicStampedReference解决。
4.2 LongAdder的性能秘密
在超高并发场景下,LongAdder通过分段累加最后汇总的方式,比AtomicLong性能更高:
java复制LongAdder counter = new LongAdder();
// 多线程调用
counter.increment();
// 获取结果
long total = counter.sum();
5. 并发工具类的实战技巧
5.1 CountDownLatch vs CyclicBarrier
- CountDownLatch:一次性使用,主线程等待多个工作线程
- CyclicBarrier:可循环使用,多个线程互相等待
java复制// 电商订单处理示例
public class OrderProcessor {
private final CyclicBarrier barrier;
private final CountDownLatch latch;
public void process(List<Order> orders) {
latch = new CountDownLatch(orders.size());
barrier = new CyclicBarrier(5, () ->
System.out.println("5个订单已准备好"));
orders.forEach(order ->
new Thread(() -> {
prepareOrder(order);
barrier.await(); // 等待其他订单
submitOrder(order);
latch.countDown();
}).start());
latch.await();
System.out.println("所有订单处理完成");
}
}
5.2 CompletableFuture的异步编排
java复制CompletableFuture.supplyAsync(() -> queryFromDB(id))
.thenApplyAsync(result -> enrichData(result))
.thenAcceptAsync(enriched -> sendNotification(enriched))
.exceptionally(ex -> {
log.error("处理失败", ex);
return null;
});
6. 线程安全的单例模式演进
- 饿汉式(类加载时初始化)
java复制public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
- 双重检查锁(延迟加载)
java复制public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 枚举实现(最安全)
java复制public enum Singleton {
INSTANCE;
public void businessMethod() {}
}
7. 死锁分析与预防
7.1 死锁产生的四个必要条件
- 互斥条件
- 请求与保持
- 不剥夺条件
- 循环等待
7.2 诊断工具
- jstack:查看线程栈
- VisualVM:图形化分析
- 代码规范:统一获取锁的顺序
java复制// 错误的锁顺序
Thread 1: 获取锁A -> 尝试获取锁B
Thread 2: 获取锁B -> 尝试获取锁A
// 正确的锁顺序
Thread 1: 获取锁A -> 获取锁B
Thread 2: 获取锁A -> 获取锁B
8. 并发编程性能调优
8.1 上下文切换成本
测试表明,上下文切换耗时在1-10微秒级别。减少切换的方法:
- 降低线程数(理想线程数 = CPU核数 * (1 + 等待时间/计算时间))
- 使用协程(Quasar库)
- 减少锁竞争
8.2 锁优化技巧
- 缩小同步范围
- 降低锁粒度(如ConcurrentHashMap)
- 读写分离(ReentrantReadWriteLock)
- 无锁设计(原子类)
java复制// 读写锁使用示例
public class Cache {
private final Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public Object get(String key) {
rwl.readLock().lock();
try {
return map.get(key);
} finally {
rwl.readLock().unlock();
}
}
public void put(String key, Object value) {
rwl.writeLock().lock();
try {
map.put(key, value);
} finally {
rwl.writeLock().unlock();
}
}
}
9. Java内存模型深度解析
9.1 happens-before规则
- 程序顺序规则
- 锁规则
- volatile规则
- 线程启动规则
- 线程终止规则
- 中断规则
- 终结器规则
- 传递性
9.2 内存屏障类型
| 屏障类型 | 说明 |
|---|---|
| LoadLoad | 保证Load1在Load2之前执行 |
| StoreStore | 保证Store1在Store2之前执行 |
| LoadStore | 保证Load在Store之前执行 |
| StoreLoad | 保证Store在Load之前执行(全能屏障) |
10. 并发测试与问题复现
10.1 压力测试工具
- JMeter:模拟高并发请求
- Stress测试:CPU密集型测试
java复制// 使用CountDownLatch模拟并发
public class ConcurrentTest {
void testConcurrentAccess() throws InterruptedException {
int threadCount = 100;
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
startLatch.await();
try {
// 执行测试逻辑
} finally {
endLatch.countDown();
}
}).start();
}
startLatch.countDown(); // 同时释放所有线程
endLatch.await(); // 等待所有线程完成
}
}
10.2 确定性测试工具
- Thread.sleep的替代方案:
java复制// 使用awaitility库进行条件等待
await().atMost(5, SECONDS).until(() ->
result.size() == expectedCount);
- 使用Mock时间:
java复制// 使用Mockito模拟时间
Clock mockClock = mock(Clock.class);
when(mockClock.millis()).thenReturn(1000L, 2000L, 3000L);
在金融支付系统开发中,我曾通过系统性的并发测试发现了一个潜伏的竞态条件:在特定时序下,余额校验和扣款操作可能被颠倒执行。这个案例让我深刻认识到,没有经过严格并发测试的系统就像没有经过风洞测试的飞机,随时可能在高并发气流中解体。
