加锁是解决java线程安全问题最直接方式,需明确锁对象、方式及时长;synchronized适用于基础场景,reentrantlock提供更灵活控制;须防死锁、兼顾可见性与有序性。

加锁是解决Java线程安全问题最直接、最常用的方式,核心目标是让临界区操作具备原子性、可见性和有序性。关键不在于“加不加锁”,而在于“锁什么、怎么锁、锁多久”。
用 synchronized 保证方法或代码块的互斥执行
synchronized 是 JVM 内置的重量级锁,使用简单且自动释放,适合大多数基础场景。
- 修饰实例方法:锁的是当前对象(this),多个线程调用同一对象的不同 synchronized 实例方法时会串行执行
- 修饰静态方法:锁的是当前类的 Class 对象,对所有该类实例全局生效
- 同步代码块:可指定任意非 null 对象作为锁,粒度更细、灵活性更高,例如 synchronized(obj) { ... }
注意:锁对象必须是多个线程共享的同一个引用;若每次 new 一个新对象当锁,就起不到同步作用。
用 ReentrantLock 实现更可控的显式加锁
ReentrantLock 属于 java.util.concurrent.locks 包,提供了比 synchronized 更丰富的功能。
- 支持尝试获取锁(tryLock()),可设超时避免无限等待
- 支持可中断的锁获取(lockInterruptibly()),便于响应线程中断
- 支持公平锁与非公平锁选择(构造时传入 true/false)
- 必须手动在 finally 块中调用 unlock(),否则极易导致死锁或资源泄漏
典型写法:
lock.lock();try {
// 临界区代码
} finally {
lock.unlock();
}
避免因多把锁引发死锁
当需要同时持有多个锁时,顺序错误是死锁主因。预防要点很明确:
- 所有线程以**完全相同的全局顺序**获取锁,例如按锁对象的 System.identityHashCode() 升序排列
- 避免在持有一把锁期间去申请另一把锁,尤其不要在 synchronized 块内调用可能加锁的外部方法
- 优先使用无锁工具类,如 AtomicInteger 替代 int + 锁,ConcurrentHashMap 替代 HashMap + 外部同步
加锁不是万能解,需配合其他机制
仅靠加锁无法覆盖所有线程安全问题:
- 内存可见性问题(如一个线程改值,另一个线程看不到最新值)仍需 volatile 或锁本身的 happens-before 语义保障
- 指令重排序影响单例双重检查等场景,volatile 关键字对 instance 字段的修饰必不可少
- 复合操作(如“先查后删”“读-改-写”)即使单步原子,整体仍不安全,必须包裹在同一个锁下完成
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











