
1. 线程安全问题的本质与根源当多个线程同时访问共享资源时如果没有正确的同步机制就会出现各种匪夷所思的问题。我曾在生产环境遇到过这样一个案例一个简单的计数器在多线程环境下运行理论上应该累加到10000实际却只得到8732。这种消失的数值就是典型的线程安全问题。线程安全的三大核心挑战1.1 原子性破坏原子性指的是一个操作不可被中断的特性。在Java中即使是简单的i操作实际上包含三个步骤读取变量i的值将i的值加1将新值写回i当多个线程同时执行这个操作时就可能出现以下情况线程A读取i1 线程B读取i1 线程A写入i2 线程B写入i2 // 最终结果i2而不是预期的3重要提示在32位JVM上long和double的读写可能不具备原子性因为它们是64位数据类型。1.2 可见性问题现代CPU架构中每个线程都有自己的工作内存CPU缓存这导致了可见性问题。一个线程对共享变量的修改可能不会立即对其他线程可见。// 典型示例 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; // 主线程修改 } }1.3 指令重排序编译器和处理器会对指令进行重排序优化这在单线程下没有问题但在多线程环境下可能导致意外结果。经典案例是单例模式的双重检查锁定问题public class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance null) { // 第一次检查 synchronized (Singleton.class) { if (instance null) { // 第二次检查 instance new Singleton(); // 问题所在 } } } return instance; } }问题在于new Singleton()不是原子操作可能被重排序为分配内存空间将引用指向内存空间此时instance!null初始化对象2. Java内存模型(JMM)深度解析2.1 Happens-Before原则JMM通过happens-before关系定义内存可见性规则主要包括程序顺序规则监视器锁规则volatile变量规则线程启动规则线程终止规则中断规则终结器规则传递性2.2 内存屏障类型屏障类型说明对应Java操作LoadLoad保证Load1先于Load2volatile读StoreStore保证Store1先于Store2volatile写LoadStore保证Load先于Store普通读写StoreLoad保证Store先于Loadvolatile写后接volatile读3. 线程安全解决方案实战3.1 锁机制比较锁类型实现方式特点适用场景synchronizedJVM内置锁自动释放不可中断简单同步场景ReentrantLockAQS实现可中断可定时公平/非公平复杂同步需求StampedLock乐观读策略读不阻塞写读多写少场景ReadWriteLock读写分离读读不互斥频繁读偶尔写3.2 volatile适用场景volatile最适合的状态标志模式public class ShutdownRequested { private volatile boolean shutdown; public void shutdown() { shutdown true; } public void doWork() { while(!shutdown) { // 执行任务 } } }注意volatile不能保证复合操作的原子性比如i这种操作。3.3 原子类性能对比// 性能测试对比 public class AtomicBenchmark { private long counter 0; private volatile long volatileCounter 0; private AtomicLong atomicCounter new AtomicLong(0); private LongAdder adder new LongAdder(); // 测试方法省略... }测试结果ops/ms基本类型1523volatile843AtomicLong672LongAdder28454. 并发容器选型指南4.1 Map实现对比实现类线程安全机制特点适用场景Hashtable全表锁过时方案遗留系统Collections.synchronizedMap全表锁包装器模式简单同步需求ConcurrentHashMap分段锁CAS高并发读写高并发环境ConcurrentSkipListMap跳表结构有序Map需要排序的场景4.2 阻塞队列选择// 队列特性比较 BlockingQueueString bq1 new ArrayBlockingQueue(100); // 有界 BlockingQueueString bq2 new LinkedBlockingQueue(); // 可选有界 BlockingQueueString bq3 new PriorityBlockingQueue(); // 优先级 BlockingQueueString bq4 new SynchronousQueue(); // 直接传递 BlockingQueueString bq5 new DelayQueue(); // 延迟元素5. 实战中的陷阱与解决方案5.1 死锁预防四法则固定顺序获取锁使用tryLock超时机制开放调用避免在持有锁时调用外部方法使用更高级的并发工具5.2 线程池参数调优ThreadPoolExecutor executor new ThreadPoolExecutor( 4, // 核心线程数 8, // 最大线程数 60, // 空闲时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue(100), // 工作队列 new ThreadFactory() { // 线程工厂 private AtomicInteger count new AtomicInteger(0); public Thread newThread(Runnable r) { return new Thread(r, worker- count.incrementAndGet()); } }, new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );5.3 性能优化技巧减小锁粒度如ConcurrentHashMap的分段锁设计降低锁竞争使用ThreadLocal缓存线程私有数据避免热点字段如LongAdder的分段累加读写分离CopyOnWriteArrayList的写时复制6. 现代Java并发新特性6.1 CompletableFuture组合式编程CompletableFuture.supplyAsync(() - queryFromDB()) .thenApplyAsync(result - processData(result)) .thenAcceptAsync(processed - saveToCache(processed)) .exceptionally(ex - { log.error(处理失败, ex); return null; });6.2 VarHandle内存操作class Point { private volatile int x; private static final VarHandle X; static { try { X MethodHandles.lookup() .findVarHandle(Point.class, x, int.class); } catch (Exception e) { throw new Error(e); } } public void increment() { int oldValue; do { oldValue x; } while(!X.compareAndSet(this, oldValue, oldValue 1)); } }6.3 虚拟线程(Loom)前瞻try (var executor Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i - { executor.submit(() - { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); } // 自动等待所有任务完成7. 线程安全设计模式7.1 不可变对象模式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方法没有setter public int getX() { return x; } public int getY() { return y; } // 返回新对象而不是修改状态 public ImmutablePoint move(int dx, int dy) { return new ImmutablePoint(x dx, y dy); } }7.2 线程特定存储模式public class UserContextHolder { private static final ThreadLocalUser context new ThreadLocal(); public static void set(User user) { context.set(user); } public static User get() { return context.get(); } public static void clear() { context.remove(); } } // 使用示例 UserContextHolder.set(currentUser); try { // 业务处理 } finally { UserContextHolder.clear(); // 防止内存泄漏 }8. 性能监控与诊断8.1 常用诊断命令jstack pid # 查看线程栈 jmap -histo pid # 对象内存统计 jstat -gcutil pid # GC统计 jcmd pid Thread.print # 线程转储8.2 常见死锁模式锁顺序死锁线程A持有锁1请求锁2线程B持有锁2请求锁1资源死锁多个线程循环等待资源线程饥饿死锁单线程池中任务提交新任务重入死锁不可重入锁的重入尝试8.3 JFR监控示例// 启动JFR记录 try (var recording new Recording()) { recording.enable(jdk.CPULoad); recording.enable(jdk.JavaMonitorEnter); recording.start(); // 执行业务代码 doBusinessLogic(); recording.stop(); recording.dump(Paths.get(recording.jfr)); }9. 并发测试实践9.1 JCStress测试框架JCStressTest Outcome(id 1, 1, expect Expect.ACCEPTABLE_INTERESTING) State public class AtomicityTest { private int x; Actor public void actor1() { x; } Actor public void actor2() { x; } Arbiter public void arbiter(IntResult2 r) { r.r1 x; r.r2 x; } }9.2 并发测试策略安全性测试验证不变性条件活跃性测试验证进展性性能测试吞吐量、响应时间、可伸缩性边界测试临界条件测试10. 架构层面的线程安全10.1 并发级别设计并发级别实现方式示例无状态不共享任何状态Servlet线程封闭栈封闭/ThreadLocalSimpleDateFormat不可变对象final字段String安全发布正确初始化单例模式同步机制锁/原子变量ConcurrentHashMap10.2 领域驱动设计中的并发聚合根作为并发边界乐观并发控制版本号悲观并发控制数据库锁事件溯源模式CQRS读写分离在实际项目中我通常会根据业务特点选择不同的线程安全策略。对于配置类数据优先考虑不可变对象对于高频计数器LongAdder是更好的选择而对于复杂业务状态可能需要结合ReentrantLock和条件队列来实现精确控制。记住没有放之四海而皆准的方案只有最适合当前场景的选择。