synchronized 关键在锁对对象和范围:禁用局部变量或 new object() 作锁,优先用 private final 对象锁,只同步必要代码段;推荐 juc 工具类如 atomicinteger、concurrenthashmap 等替代手写锁。

用 synchronized 锁住临界区,但别锁错对象
多数线程安全问题出在共享变量被多个线程并发读写,synchronized 是最直接的解决方式。关键不是“加不加锁”,而是“锁谁”和“锁多大范围”。
常见错误:用局部变量、临时对象或 new Object() 做锁——根本起不到互斥作用,因为每次调用都新建一个锁对象;或者把整个方法都同步(public synchronized void doWork()),导致不必要的串行化,吞吐量骤降。
- 实例方法同步等价于
synchronized(this),适用于保护当前对象的状态 - 静态方法同步等价于
synchronized(YourClass.class),用于保护类级别的共享资源 - 优先用私有 final 对象锁(如
private final Object lock = new Object();),避免外部误锁或反射干扰 - 只包裹真正需要互斥的代码段,比如只锁
count++那一行,而不是把日志、IO 全包进去
java.util.concurrent 里的工具类比手写锁更可靠
自己用 synchronized 或 ReentrantLock 容易出错:忘记 unlock、死锁、条件等待逻辑错乱。JUC 包里封装了大量经过充分验证的线程安全类型,能省掉大量底层细节。
比如要实现一个计数器,别再写 synchronized int count + 手动增减;直接用 AtomicInteger,它的 incrementAndGet() 是 CPU 级原子指令,无锁且高效。
立即学习“Java免费学习笔记(深入)”;
KgShop,是国内一款快速/稳定/安全的开源电子商城系统,采用linux,mysql,srutsEX,hibernate,ejb3等技术,Kghop第一版诞生于2010年,经过多年开发,Kgshop系统已拥有快速、稳定、支持大量并发访问等软件特性,是10万人在线的JAVA商城优秀解决方案。KgShop拥有良好的模板机制,易于进行二次开发。Kgshop每一行代码都经过严谨的测试,汇聚大批工程师多年
-
ConcurrentHashMap不是HashMap加个锁,它分段锁 + CAS,读操作完全无锁,写冲突概率低 -
CopyOnWriteArrayList适合读多写少场景(如监听器列表),写时复制数组,读不阻塞,但注意内存开销和迭代器看到的是快照 -
ThreadLocal<t></t>不是共享,而是“每人一份”,适用于传递上下文(如用户 ID、事务 ID),但记得remove()防止线程池复用导致内存泄漏
不可变对象天然线程安全,但得真不可变
如果一个对象构造后所有字段都是 final,且引用的内部对象也都不对外暴露可变状态,那它就无需任何同步——JVM 保证 final 字段的初始化安全发布。
但很多人以为加了 final 就万事大吉。比如 final List<string> list = new ArrayList();</string>,list 引用不可变,但内容仍可被修改,这不是真正的不可变。
- 字段必须全部
final - 若包含可变对象(如
ArrayList、StringBuilder),要么不暴露其引用,要么返回不可变包装(Collections.unmodifiableList()) - 构造函数里不能泄露
this(比如注册监听、启动线程),否则可能看到未初始化完成的对象 - 推荐用
Records(Java 14+)快速定义不可变数据载体,编译器自动加final和防御性拷贝
volatile 解决可见性,但不解决原子性
volatile 常被误当成“轻量级 synchronized”。它只保证两点:写操作对其他线程立即可见;禁止指令重排序。但它不提供原子性——volatile int counter; 后面写 counter++,仍是读-改-写三步,依然会丢更新。
典型适用场景是状态标志位:开关控制、初始化完成标记、中断信号等单次写入、多次读取的布尔值。
- 正确用法:
private volatile boolean shutdownRequested = false;+ 其他线程检查该值退出循环 - 错误用法:
volatile int count;+count++—— 依旧线程不安全 - 与
AtomicInteger对比:后者底层用volatile+ CAS 实现原子操作,volatile单独做不到 - 注意:
long和double的非原子性读写问题在 64 位 JVM 上基本不存在,但加volatile仍是最稳妥做法
线程安全不是靠堆砌关键字或工具,而是从数据归属出发:这个变量到底由谁负责?是否必须共享?能否隔离?能否只读?很多问题在设计阶段想清楚,比后期加锁补救成本低得多。特别注意线程池中复用对象、静态变量跨请求污染、以及第三方库内部状态这些隐性共享点。








