应优先使用线程安全工具类而非手动同步,如atomicinteger、concurrenthashmap;确保锁对象正确(避免锁局部变量或字符串字面量);无状态或不可变对象天然线程安全;警惕spring单例bean中隐式共享可变状态。

用 synchronized 保护共享状态,但别锁错对象
多数线程安全问题源于多个线程同时读写同一份实例变量。最直接的解法是加锁,但常见错误是锁了局部变量或新创建的对象——这根本不起作用。
-
synchronized(this)或synchronized实例方法:只对当前对象实例有效,不同对象实例之间不互斥 - 静态方法或
synchronized(ClassName.class):适用于保护类级别的共享资源(如静态计数器) - 避免锁
new Object()或字符串字面量(如synchronized("lock")),后者可能因字符串常量池导致意外锁竞争
例如:ConcurrentHashMap 内部按 segment 分段加锁,比全局锁 Hashtable 更高效;而盲目给整个方法加 synchronized 可能成为性能瓶颈。
优先使用线程安全的工具类,而不是自己同步
JDK 提供的并发包(java.util.concurrent)不是摆设。自己用 synchronized 包裹 ArrayList 或手写双重检查单例,往往掩盖了更深层的设计问题。
- 计数场景用
AtomicInteger,而非int+ 手动同步 - 共享集合优先选
ConcurrentHashMap、CopyOnWriteArrayList(注意后者适合读多写少) - 需要协调执行顺序时,用
CountDownLatch或CyclicBarrier,而不是靠wait/notify自己拼逻辑
这些类内部已做大量优化,比如 AtomicInteger.incrementAndGet() 底层调用 CPU 的 CAS 指令,比锁开销小得多。
立即学习“Java免费学习笔记(深入)”;
无状态或不可变对象天然线程安全
如果一个对象创建后所有字段都不再修改(final 修饰,且引用的内部对象也不可变),那它在多线程下完全不需要同步。
- 把可变状态抽离到局部变量或线程局部存储(
ThreadLocal)中 - 避免在对象构造完成后再通过 setter 修改关键字段
- 谨慎使用
StringBuilder:它是可变的,若在多线程间共享并调用append(),就会出问题;此时应改用StringBuffer或每个线程用独立实例
像 LocalDateTime、BigDecimal 这类不可变类型,传参、返回、缓存都放心,不用考虑同步。
警惕隐式共享:线程池 + 实例变量 = 定时炸弹
Spring 等框架默认把 Bean 设为单例,如果这个 Bean 里持有 SimpleDateFormat、ArrayList 等可变成员,并被线程池中的多个线程反复调用,问题就藏不住了。
-
SimpleDateFormat不是线程安全的,不要作为类字段复用;要么每次新建,要么用DateTimeFormatter(JDK8+,不可变) - 避免在 Runnable/Callable 实现类中直接引用外部对象的可变字段
- 用
@Scope("prototype")或ThreadLocal隔离状态,比事后加锁更干净
真正难调试的并发 bug 往往不是锁没加,而是你以为没共享,其实已经共享了——比如日志上下文、用户会话、数据库连接参数等看似“临时”的东西,一旦被多个请求线程交叉写入,结果就不可预测。









