1. 并发编程基础概念扫盲第一次接触并发编程时我被各种术语搞得晕头转向。后来在实际项目中踩过无数坑才明白所谓并发(Concurrency)就是让计算机同时处理多个任务的能力。注意这里的引号——在单核CPU上所谓的同时其实是快速切换执行不同任务造成的假象。举个例子就像餐厅里的一位服务员同时照看多张桌子。他并不是真的在同一时刻为所有顾客服务而是在各桌之间快速轮转给A桌上菜后立刻去B桌点单。这种时间分片(time slicing)机制就是并发的基础实现方式。1.1 线程的本质剖析线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程中的实际运作单位。每个线程都有自己的程序计数器、栈和局部变量但共享堆内存和全局变量。我在调试一个Java应用时曾用jstack命令看到这样的线程栈main #1 prio5 os_prio0 tid0x00007f4874009800 nid0x1a3 waiting on condition [0x00007f487b4e4000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.example.MyApp.main(MyApp.java:10)这段输出揭示了线程的几个关键特征每个线程有唯一标识(tid)和原生线程ID(nid)线程有状态概念(如TIMED_WAITING)栈轨迹显示了线程当前执行位置1.2 并发与并行的本质区别很多初学者容易混淆并发(Concurrency)和并行(Parallelism)。通过一个实际案例说明假设我们要处理1000张图片并发方案用4个线程在单核CPU上轮流处理每个线程处理250张并行方案用4个线程在4核CPU上同时处理每个线程处理250张关键区别在于是否真正同时执行。现代CPU通常结合两种方式——多核并行执行每个核上又通过时间分片实现并发。2. 线程生命周期与状态转换理解线程的各种状态对调试并发问题至关重要。Java中线程状态定义在Thread.State枚举中public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }2.1 状态转换全图解我在项目中总结的完整状态转换流程NEW线程刚创建尚未启动RUNNABLE调用start()后进入可运行状态包括正在运行和就绪两种子状态BLOCKED等待获取监视器锁(同步块)WAITING无限期等待其他线程显式唤醒通过Object.wait()或Thread.join()进入TIMED_WAITING有限时间的等待通过Thread.sleep()或带超时的wait()/join()TERMINATED线程执行完毕关键经验BLOCKED和WAITING状态最容易引发性能问题和死锁需要特别关注2.2 状态监测实战技巧排查线上问题时我常用以下方法检查线程状态jstack工具jstack pid thread_dump.log分析各线程状态分布特别关注BLOCKED和WAITING线程数VisualVM监控实时查看线程状态饼图点击具体线程查看栈轨迹代码级检查Thread thread ...; Thread.State state thread.getState();3. 等待通知机制深度解析等待通知机制是多线程协作的核心模式。其本质是通过条件队列(Condition Queue)实现线程间通信。3.1 经典生产者-消费者问题我们通过一个实际案例理解该机制。假设开发一个日志处理系统class LogQueue { private QueueString queue new LinkedList(); private int maxSize 10; public synchronized void produce(String log) throws InterruptedException { while (queue.size() maxSize) { wait(); // 队列满时等待 } queue.add(log); notifyAll(); // 唤醒消费者 } public synchronized String consume() throws InterruptedException { while (queue.isEmpty()) { wait(); // 队列空时等待 } String log queue.poll(); notifyAll(); // 唤醒生产者 return log; } }这段代码展示了等待通知机制的三个核心操作条件检查while循环检查条件(不能用if)等待调用wait()释放锁并进入WAITING状态通知条件变化后调用notify()/notifyAll()血泪教训一定要在while循环中检查条件我曾因使用if导致罕见的竞态条件线上出现日志丢失。3.2 wait()与sleep()的本质区别很多新手容易混淆这两个方法通过对比表说明特性wait()sleep()所属类ObjectThread锁行为释放锁不释放锁唤醒条件需其他线程notify()超时自动唤醒使用场景线程间协作单纯暂停执行异常处理需处理InterruptedException需处理InterruptedException实际项目中我曾误用sleep()导致死锁线程A持有锁后sleep()线程B因获取不到锁而阻塞。4. 线程安全与锁优化实战保证线程安全是并发编程的核心挑战。我们通过一个电商库存扣减案例说明。4.1 超卖问题分析初始实现public class Inventory { private int stock 100; public void deduct() { if (stock 0) { stock--; } } }这个实现在并发场景下会导致超卖(stock可能变为负数)。原因在于check-then-act操作不是原子的。4.2 同步方案对比方案一synchronized方法public synchronized void deduct() { if (stock 0) { stock--; } }方案二ReentrantLockprivate Lock lock new ReentrantLock(); public void deduct() { lock.lock(); try { if (stock 0) { stock--; } } finally { lock.unlock(); } }方案三Atomic变量private AtomicInteger stock new AtomicInteger(100); public void deduct() { stock.updateAndGet(x - x 0 ? x - 1 : x); }性能对比(10万次操作)方案耗时(ms)适用场景synchronized125简单同步ReentrantLock98需要高级功能(如公平锁)Atomic45简单原子操作实际选择我通常在简单场景用Atomic复杂业务用ReentrantLock遗留代码维护用synchronized4.3 锁优化技巧减小锁粒度// 不好 public synchronized void process() { // 全部代码加锁 } // 优化后 public void process() { // 无锁代码 synchronized(this) { // 最小临界区 } // 无锁代码 }锁分离技术class BetterMap { private final MapString, String map new HashMap(); private final ReentrantReadWriteLock rwl new ReentrantReadWriteLock(); public String get(String key) { rwl.readLock().lock(); try { return map.get(key); } finally { rwl.readLock().unlock(); } } public void put(String key, String value) { rwl.writeLock().lock(); try { map.put(key, value); } finally { rwl.writeLock().unlock(); } } }避免锁嵌套 我曾遇到一个死锁案例// 线程A synchronized(lock1) { synchronized(lock2) { // ... } } // 线程B synchronized(lock2) { synchronized(lock1) { // ... } }解决方案统一获取锁的顺序或使用tryLock()超时机制。5. 线程池原理与最佳实践线程池是管理线程生命周期的利器。我们深入分析ThreadPoolExecutor的实现。5.1 核心参数解析public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler )关键参数corePoolSize核心线程数(即使空闲也不会回收)maximumPoolSize最大线程数限制workQueue任务队列(常见有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue)handler拒绝策略(AbortPolicy、CallerRunsPolicy等)5.2 线程池工作流程提交任务时优先创建核心线程执行核心线程全忙时任务进入队列队列满时创建非核心线程(不超过maximumPoolSize)线程数达上限且队列满时触发拒绝策略我在监控系统看到的典型问题队列无限增长使用无界队列(如LinkedBlockingQueue)导致OOM线程爆炸maximumPoolSize设置过大耗尽资源任务堆积核心线程数设置过小5.3 配置建议根据业务类型推荐配置业务类型核心线程数最大线程数队列类型拒绝策略CPU密集型CPU核数1CPU核数*2ArrayBlockingQueueCallerRunsPolicyIO密集型CPU核数*2CPU核数*4SynchronousQueueAbortPolicy混合型CPU核数*3CPU核数*6LinkedBlockingQueueDiscardOldest实际案例我们的支付系统使用IO密集型配置核心线程数8(4核服务器)最大线程数16队列大小100拒绝策略记录日志后降级处理6. 常见并发问题排查指南6.1 死锁检测与解决典型死锁特征多个线程BLOCKED状态每个线程持有某个锁同时等待另一个锁诊断步骤使用jstack获取线程转储查找deadlock关键词分析锁的持有/等待关系解决方案锁顺序一致性使用tryLock()带超时减少锁粒度6.2 活锁与饥饿**活锁(Livelock)**案例// 两个线程互相礼让 while (!tryGetLock()) { Thread.sleep(100); // 主动释放CPU // 两个线程同步sleep导致永远无法获取锁 }**饥饿(Starvation)**案例 低优先级线程长期得不到执行常见于不公平锁线程优先级设置不当6.3 性能问题排查典型症状CPU使用率高但吞吐量低大量线程处于BLOCKED状态工具链top查看整体CPU使用jstack分析线程状态分布Arthas监控方法执行时间VisualVM查看锁竞争情况优化方向减少锁竞争(如用ConcurrentHashMap)降低锁粒度使用读写锁分离考虑无锁数据结构7. Java内存模型(JMM)与happens-before理解JMM是写出正确并发程序的基础。核心原则是happens-before关系。7.1 八大happens-before规则程序顺序规则同一线程中的操作按程序顺序执行锁规则解锁操作happens-before后续加锁操作volatile规则写操作happens-before后续读操作线程启动规则线程A启动线程B那么A的操作happens-beforeB的任何操作线程终止规则线程A等待线程B终止B的操作happens-beforeA的后续操作中断规则对线程interrupt()的调用happens-before被中断线程检测到中断终结器规则对象构造函数happens-before它的finalize()方法传递性如果A happens-before B且B happens-before C那么A happens-before C7.2 volatile关键字深入volatile保证可见性和禁止指令重排序但不保证原子性。典型使用场景class Worker implements Runnable { private volatile boolean running true; public void stop() { running false; } Override public void run() { while (running) { // 执行任务 } } }常见误区认为volatile可以替代锁(实际上它不保证复合操作的原子性)过度使用导致性能下降(volatile写比普通写慢)7.3 双重检查锁定模式经典的单例实现问题class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance null) { // 第一次检查 synchronized (Singleton.class) { if (instance null) { // 第二次检查 instance new Singleton(); } } } return instance; } }这个实现在Java 1.5之前有缺陷因为new操作可能被重排序。解决方案使用volatile修饰instance改用静态内部类方式class Singleton { private static class Holder { static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }8. 并发工具类实战Java并发包(java.util.concurrent)提供了强大的工具类。8.1 CountDownLatch应用典型场景主线程等待多个子线程完成初始化class ServiceInitializer { private final CountDownLatch latch new CountDownLatch(3); public void init() throws InterruptedException { new Thread(() - { initDatabase(); latch.countDown(); }).start(); new Thread(() - { initCache(); latch.countDown(); }).start(); new Thread(() - { initAPI(); latch.countDown(); }).start(); latch.await(); // 等待所有初始化完成 System.out.println(所有服务初始化完成); } }8.2 CyclicBarrier vs CountDownLatch对比两种同步工具特性CountDownLatchCyclicBarrier重置不可重置可循环使用计数方向递减递增等待方主线程等待子线程所有线程互相等待典型应用场景启动准备、结束等待分阶段并行计算8.3 CompletableFuture异步编程现代Java异步编程首选CompletableFuture.supplyAsync(() - queryFromDatabase()) .thenApplyAsync(data - processData(data)) .thenAcceptAsync(result - sendResult(result)) .exceptionally(ex - { logger.error(处理失败, ex); return null; });优势链式调用避免回调地狱灵活的组合多个异步任务内置异常处理机制我在订单系统中用CompletableFuture实现并行查询用户信息和商品库存合并结果后验证异步记录日志 性能提升40%以上9. 并发设计模式实践9.1 不变性模式通过不可变对象避免同步Immutable public final class Product { private final String id; private final String name; private final BigDecimal price; // 全参构造函数 // 只有getter方法 }优点天然线程安全无需同步可以自由共享和缓存9.2 线程局部存储ThreadLocal典型应用public class UserContextHolder { private static final ThreadLocalUser holder new ThreadLocal(); public static void set(User user) { holder.set(user); } public static User get() { return holder.get(); } public static void remove() { holder.remove(); } }使用场景用户会话信息传递数据库连接管理避免方法参数层层传递重要必须及时remove()否则可能导致内存泄漏特别是线程池场景9.3 生产者-消费者模式基于BlockingQueue的高级实现public class LogProcessor { private final BlockingQueueString queue new LinkedBlockingQueue(1000); private final ListThread workers new ArrayList(); public void start(int workerCount) { for (int i 0; i workerCount; i) { Thread worker new Thread(() - { while (!Thread.currentThread().isInterrupted()) { try { String log queue.take(); processLog(log); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); worker.start(); workers.add(worker); } } public void stop() { workers.forEach(Thread::interrupt); } public void acceptLog(String log) throws InterruptedException { queue.put(log); } }10. 现代并发编程趋势10.1 协程与虚拟线程Java 19引入的虚拟线程(协程)示例try (var executor Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i - { executor.submit(() - { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }与传统线程对比创建成本极低(数量可达百万级)由JVM调度非OS线程适合IO密集型任务10.2 响应式编程Spring WebFlux示例RestController public class UserController { GetMapping(/users) public FluxUser listUsers() { return userRepository.findAll() .subscribeOn(Schedulers.boundedElastic()) .delayElements(Duration.ofMillis(100)); } }特点基于事件驱动非阻塞IO背压支持10.3 无锁编程AtomicReferenceFieldUpdater示例class Counter { private volatile int value; private static final AtomicIntegerFieldUpdaterCounter updater AtomicIntegerFieldUpdater.newUpdater(Counter.class, value); public int increment() { return updater.incrementAndGet(this); } }适用场景高竞争下的计数器状态标志更新链表节点的原子插入在实际性能测试中无锁方案在高度竞争场景下比锁方案快5-10倍但在低竞争时可能更慢。