1. Java并发编程的核心价值与应用场景
第一次接触Java并发编程是在2013年处理一个电商秒杀系统时。当单机QPS突破2000后,各种诡异的线程安全问题接踵而至——库存超卖、订单重复、死锁频发。这段经历让我深刻认识到:在分布式和高并发成为标配的今天,掌握并发编程不是加分项,而是Java工程师的生存技能。
现代Java应用几乎都运行在多核CPU上,服务器动辄32核甚至64核。如果代码不能有效利用多核优势,就相当于花高价买了跑车却只用一档行驶。典型的并发应用场景包括:
- 电商系统的库存扣减和订单处理
- 金融交易系统的实时风控计算
- 物联网设备的海量数据采集
- 社交媒体的feed流生成
注意:并发(Concurrency)不等于并行(Parallelism)。并发是逻辑上的同时处理,并行是物理上的同时执行。单核CPU通过时间片轮转实现并发,多核CPU才能实现真正的并行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java内存模型(JMM)深度解析
2.1 主内存与工作内存的交互协议
Java内存模型规定所有变量存储在主内存,每个线程有独立的工作内存。线程对变量的操作必须遵循以下规则:
- read:从主内存读取到工作内存
- load:将read的值放入工作内存变量副本
- use:线程使用工作内存中的变量值
- assign:线程将新值赋给工作内存变量
- store:将工作内存变量值传送到主内存
- write:将store得到的值写入主内存变量
java复制// 典型的内存可见性问题示例
public class VisibilityProblem {
private static boolean ready = false;
private static int number = 0;
public static void main(String[] args) {
new Thread(() -> {
while(!ready) {
// 可能永远循环
}
System.out.println(number);
}).start();
number = 42;
ready = true;
}
}
2.2 happens-before原则精要
JMM通过happens-before定义操作间的偏序关系,确保正确同步时内存可见性:
- 程序顺序规则:线程内每个操作happens-before后续操作
- 监视器锁规则:解锁happens-before后续加锁
- volatile规则:写happens-before后续读
- 传递性规则:A happens-before B,B happens-before C,则A happens-before C
3. 线程安全的三层防御体系
3.1 无状态对象
最简单的线程安全策略。无状态对象不包含任何字段,也不引用其他对象的字段。所有数据都来自参数,不会出现共享数据竞争。
java复制// 无状态Servlet示例
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
3.2 不可变对象
不可变对象创建后状态不可改变,是天然的线程安全对象。实现要点:
- 所有字段final
- 正确构造(防止this引用逸出)
- 不提供修改状态的方法
java复制// 不可变Point类
@Immutable
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// 只有getter方法
}
3.3 同步策略
当必须使用可变状态时,需要同步机制保证线程安全:
3.3.1 synchronized内置锁
java复制// 同步计数器
public class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
3.3.2 ReentrantLock显式锁
java复制// 使用ReentrantLock的计数器
public class ReentrantLockCounter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
实战经验:synchronized在JDK6后性能已大幅优化,除非需要高级功能(如可中断、公平锁等),否则优先使用synchronized。
4. 并发容器全景解析
4.1 ConcurrentHashMap实现原理
JDK8中的ConcurrentHashMap采用Node数组+链表/红黑树结构,通过以下设计保证并发性能:
- 分段锁思想(JDK7)升级为CAS+synchronized(JDK8)
- 扩容时多线程协同转移数据
- 统计size时采用LongAdder思想避免竞争
java复制// 统计文本中单词频率的并发实现
public class WordFrequencyCounter {
private final ConcurrentMap<String, Long> wordCounts =
new ConcurrentHashMap<>(1024);
public void count(String word) {
wordCounts.compute(word, (k, v) -> v == null ? 1 : v + 1);
}
}
4.2 CopyOnWrite容器适用场景
适合读多写少的场景,写操作时复制整个底层数组。典型应用:
- 事件监听器列表
- 黑白名单配置
- 静态数据缓存
java复制// 使用CopyOnWriteArrayList管理监听器
public class EventSource {
private final List<EventListener> listeners =
new CopyOnWriteArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void fireEvent(Event event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
5. 线程池的七大核心参数
5.1 参数详解与配置公式
- corePoolSize:核心线程数 = CPU密集型任务:Ncpu+1 | IO密集型任务:Ncpu*2
- maximumPoolSize:最大线程数 = corePoolSize * 2
- keepAliveTime:非核心线程空闲存活时间 = 60s
- unit:时间单位 = TimeUnit.SECONDS
- workQueue:任务队列 = new LinkedBlockingQueue<>(1000)
- threadFactory:线程工厂 = Executors.defaultThreadFactory()
- handler:拒绝策略 = new ThreadPoolExecutor.AbortPolicy()
java复制// 电商订单处理线程池配置
public class OrderThreadPool {
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;
private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2;
private static final ThreadPoolExecutor ORDER_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new NamedThreadFactory("Order-Processor"),
new ThreadPoolExecutor.CallerRunsPolicy());
}
5.2 四种拒绝策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy | 抛出RejectedExecutionException | 需要明确感知任务被拒绝 |
| CallerRunsPolicy | 由调用线程执行该任务 | 不希望丢失任务且可接受降级 |
| DiscardPolicy | 静默丢弃被拒绝任务 | 允许丢失部分任务 |
| DiscardOldestPolicy | 丢弃队列中最老任务 | 允许丢弃旧任务处理新任务 |
6. 原子变量与CAS原理
6.1 Unsafe类的魔法操作
Java通过sun.misc.Unsafe类提供CAS支持,主要包含:
- compareAndSwapInt/Object等CAS操作
- park/unpark线程阻塞唤醒
- 直接内存操作
java复制// 模拟AtomicInteger实现
public class FakeAtomicInteger {
private volatile int value;
private static final Unsafe unsafe = getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(FakeAtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
6.2 ABA问题解决方案
CAS操作可能遇到ABA问题(值从A→B→A,CAS无法感知中间变化)。解决方案:
- 版本号机制(AtomicStampedReference)
- 布尔标记(AtomicMarkableReference)
java复制// 使用AtomicStampedReference解决ABA问题
public class ABASolution {
private static AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
public static void main(String[] args) {
int stamp = atomicRef.getStamp();
atomicRef.compareAndSet(100, 101, stamp, stamp + 1);
stamp = atomicRef.getStamp();
atomicRef.compareAndSet(101, 100, stamp, stamp + 1);
// 带版本号的CAS可以检测到中间变化
boolean success = atomicRef.compareAndSet(100, 101, 0, 1);
System.out.println(success); // false
}
}
7. 并发设计模式实战
7.1 生产者-消费者模式
使用BlockingQueue实现的标准生产者-消费者:
java复制public class ProducerConsumer {
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
class Producer implements Runnable {
public void run() {
while (true) {
Task task = produceTask();
try {
queue.put(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
class Consumer implements Runnable {
public void run() {
while (true) {
try {
Task task = queue.take();
processTask(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
7.2 Fork/Join框架应用
适合可分解的递归型任务,如大规模数据处理:
java复制// 使用ForkJoin计算斐波那契数列
public class Fibonacci extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) { this.n = n; }
protected Integer compute() {
if (n <= 1) return n;
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
return f2.compute() + f1.join();
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Fibonacci task = new Fibonacci(30);
System.out.println(pool.invoke(task));
}
}
8. 并发调试与性能优化
8.1 死锁检测与预防
使用jstack检测死锁:
bash复制jstack -l <pid> > thread_dump.log
预防死锁的编码规范:
- 按固定顺序获取多个锁
- 使用带超时的锁获取(tryLock)
- 避免嵌套锁
- 使用更高级的并发工具代替显式锁
8.2 并发性能优化技巧
- 减小锁粒度:拆锁→分段锁→无锁
- 降低锁竞争:使用并发容器、读写分离
- 替换同步锁:volatile+CAS→原子变量→LongAdder
- 避免伪共享:@Contended注解填充缓存行
java复制// 使用@Contended避免伪共享
public class FalseSharing {
@sun.misc.Contended
public static class VolatileLong {
public volatile long value = 0L;
}
private static VolatileLong[] longs = new VolatileLong[4];
static {
for (int i = 0; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
}
}
9. Java并发工具包进阶
9.1 CountDownLatch vs CyclicBarrier
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 计数 | 单向递减 | 可重置循环使用 |
| 等待 | 线程等待计数归零 | 线程相互等待到达屏障 |
| 用途 | 启动信号/完成信号 | 多阶段任务同步 |
java复制// 使用CyclicBarrier实现多阶段任务
public class CyclicBarrierDemo {
public static void main(String[] args) {
final int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount,
() -> System.out.println("All threads reached barrier"));
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " phase 1");
barrier.await();
System.out.println(Thread.currentThread().getName() + " phase 2");
barrier.await();
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
9.2 CompletableFuture组合式异步编程
java复制// 使用CompletableFuture实现异步流水线
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> queryCode("阿里巴巴"))
.thenApplyAsync(code -> fetchPrice(code))
.thenAccept(result -> System.out.println("价格: " + result))
.exceptionally(e -> {
System.out.println("异常: " + e.getMessage());
return null;
});
// 主线程不要立即结束
try { Thread.sleep(2000); } catch (InterruptedException e) {}
}
}
10. 并发编程常见陷阱
10.1 线程池使用误区
- 使用Executors.newFixedThreadPool导致OOM(无界队列)
- 忽略线程池拒绝策略
- 未正确关闭线程池
- 混淆不同线程池的适用场景
10.2 锁使用的典型错误
- synchronized方法锁定this对象导致意外阻塞
- 锁对象被重新赋值导致锁失效
- 嵌套锁引发死锁
- 锁粒度过粗影响性能
java复制// 错误示例:锁对象被重新赋值
public class LockInvalidation {
private Object lock = new Object();
public void wrongMethod() {
synchronized(lock) {
lock = new Object(); // 导致后续线程使用不同锁
// 临界区代码
}
}
}
在并发编程实践中,最宝贵的经验往往来自生产环境的故障复盘。建议每个Java开发者都建立自己的并发问题案例库,记录包括:问题现象、根因分析、解决方案、预防措施等关键信息。这种经验积累比单纯学习理论更能提升实战能力。
