最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
大白话说Java面试题 第106题 并发篇 第6题:synchronized 锁的锁对象可以是什么?
时间:2026-06-17 09:05:48 编辑:袖梨 来源:一聚教程网
第6题:synchronized 锁的锁对象可以是什么?
回答:

- 核心考点:
synchronized锁对象的选择是并发编程中最基础也最最容易踩坑的知识点。大厂面试不会只问"锁对象可以是类对象、实例对象、任意对象",而是深入考察 锁对象选择不当导致的死锁、性能瓶颈、锁粒度问题,以及 String 常量池、Integer 缓存池等特殊对象的锁陷阱。面试官真正想判断的是:你是否能识别常见锁对象误用场景,并给出正确的工程实践方案。
1. 三种锁对象类型与字节码实现
| 修饰位置 | 锁对象 | 字节码实现 | 锁范围 |
|---|---|---|---|
| 静态方法 | Class 对象(Example.class) | ACC_SYNCHRONIZED 标志 + Class 对象 | 整个类,所有实例共享 |
| 实例方法 | 当前实例(this) | ACC_SYNCHRONIZED 标志 + this 引用 | 单个实例 |
| 同步代码块 | 显式指定的任意对象 | monitorenter + monitorexit | 代码块范围 |
-
1.1 静态方法——类级锁
public class Counter { private static int count = 0; public static synchronized void increment() { count++; } }字节码:方法标志位
ACC_SYNCHRONIZED+ACC_STATIC,锁对象为Counter.class。 特点:所有实例、所有线程竞争同一把锁,并发度最低,但保证类级数据一致性。 -
1.2 实例方法——对象级锁
public class Counter { private int count = 0; public synchronized void increment() { count++; } }字节码:方法标志位
ACC_SYNCHRONIZED,锁对象为this。 特点:不同实例之间互不干扰,并发度高于类级锁。 -
1.3 同步代码块——灵活指定
public class Counter { private final Object lock = new Object(); private int count = 0; public void increment() { synchronized (lock) { count++; } } }字节码:
monitorenter+monitorexit指令,锁对象为lock引用指向的对象。 特点:最灵活,可精确控制锁粒度,是生产环境的首选方式。
2. 锁对象选择的五大原则
-
2.1 原则一:锁对象必须是 final 或不可变
// 错误:锁对象引用可变 private Object lock = new Object(); public void method() { synchronized (lock) { ... } } // 某处执行 lock = new Object(); → 两个线程持有不同锁,同步失效// 正确:final 保证引用不可变 private final Object lock = new Object(); -
2.2 原则二:锁对象必须是私有的
// 错误:外部可获取锁对象,导致不可控竞争 public final Object lock = new Object(); // 外部代码:synchronized(counter.lock) { ... } → 不可控死锁// 正确:私有 + final private final Object lock = new Object(); -
2.3 原则三:避免使用可变对象作为锁
// 错误:StringBuilder 内容变化后 hashCode 变化,但锁对象引用没变 private final StringBuilder lock = new StringBuilder(); // 虽然引用 final,但 StringBuilder 本身可变,语义混乱// 正确:使用专门的 Object 实例 private final Object lock = new Object(); -
2.4 原则四:避免使用可被外部访问的对象作为锁
// 错误:使用字符串字面量(常量池复用) private final String lock = "LOCK"; // 其他类也可能用 "LOCK" 作为锁 → 意外竞争// 正确:new String("LOCK") 或直接用 Object private final Object lock = new Object(); -
2.5 原则五:细粒度锁优于粗粒度锁
// 错误:一个大锁保护所有操作 public synchronized void methodA() { ... } public synchronized void methodB() { ... } // methodA 和 methodB 互不干扰,却竞争同一把锁// 正确:分离锁 private final Object lockA = new Object(); private final Object lockB = new Object(); public void methodA() { synchronized(lockA) { ... } } public void methodB() { synchronized(lockB) { ... } }
3. 常见锁对象陷阱与避坑指南
-
3.1 陷阱一:String 常量池复用
// 致命错误:不同类使用相同字符串字面量,竞争同一把锁 public class ServiceA { private final String lock = "CONFIG_LOCK"; public void update() { synchronized(lock) { ... } } } public class ServiceB { private final String lock = "CONFIG_LOCK"; // 常量池复用,同一对象! public void update() { synchronized(lock) { ... } } }原理:Java 字符串常量池会复用相同字面量,
"CONFIG_LOCK"在 JVM 中只有一份。ServiceA 和 ServiceB 实际上竞争同一把锁,可能导致意外阻塞和死锁。解决方案:
// 方案一:使用 new String() 创建独立对象 private final String lock = new String("CONFIG_LOCK");// 方案二:直接使用 Object(推荐) private final Object lock = new Object(); -
3.2 陷阱二:Integer 缓存池
// 致命错误:Integer 缓存导致锁对象相同 private final Integer lock = 100; // -128~127 缓存范围内 // 其他类:private final Integer anotherLock = 100; → 同一对象!原理:
Integer.valueOf()对 -128~127 有缓存,相同值返回同一对象。解决方案:
// 使用 new Integer() 或 Object private final Object lock = new Object(); -
3.3 陷阱三:this 锁的隐式共享
// 问题:外部可直接 synchronized(obj) 获取 this 锁 public class Counter { public synchronized void increment() { count++; } } // 外部代码: Counter c = new Counter(); synchronized(c) { // 获取了 Counter 实例的锁! c.increment(); // 重入,但语义混乱 }解决方案:
// 使用私有锁对象,隐藏锁细节 public class Counter { private final Object lock = new Object(); public void increment() { synchronized(lock) { count++; } } } -
3.4 陷阱四:集合类作为锁对象
// 问题:Collections.synchronizedList 的锁就是 list 本身 List<String> list = Collections.synchronizedList(new ArrayList<>()); synchronized(list) { // 正确,与 synchronizedList 内部锁一致 for (String s : list) { ... } // 迭代必须外部同步 } // 但如果用其他对象锁,就无法保护 list 的内部操作 -
3.5 陷阱五:Class 对象的隐式竞争
// 问题:反射和同步都可能锁定 Class 对象 public static synchronized void methodA() { ... } // 外部代码: synchronized(Example.class) { // 获取了 Class 锁! // 此时 methodA 被阻塞 }
4. 高级锁对象设计模式
-
4.1 分段锁(Segment Lock)
public class ConcurrentHashMapV7<K, V> { private static final int SEGMENT_COUNT = 16; private final Segment<K, V>[] segments; static class Segment<K, V> { private final Object lock = new Object(); private final HashMap<K, V> map = new HashMap<>(); public V put(K key, V value) { synchronized(lock) { return map.put(key, value); } } } public V put(K key, V value) { int index = hash(key) % SEGMENT_COUNT; return segments[index].put(key, value); } }原理:将数据分成多个段,每段独立加锁,不同段的写操作可并行。JDK 7 的
ConcurrentHashMap采用此设计 [citation:4]。 -
4.2 读写分离锁
public class ReadWriteData { private final Object readLock = new Object(); private final Object writeLock = new Object(); private volatile int data; public int read() { synchronized(readLock) { return data; } } public void write(int value) { synchronized(writeLock) { data = value; } } }注意:此示例中读锁和写锁分离,但读操作不互斥(多个线程可同时读)。更完善的实现应使用
ReentrantReadWriteLock。 -
4.3 按哈希值分锁
public class HashLock { private final Object[] locks = new Object[16]; public HashLock() { for (int i = 0; i < locks.length; i++) { locks[i] = new Object(); } } public void lock(Object key) { synchronized(locks[key.hashCode() % locks.length]) { // 操作 } } }适用场景:按用户 ID、订单 ID 等维度加锁,相同 ID 的操作串行,不同 ID 的操作并行。
5. 锁对象与对象头 Mark Word 的关系
锁对象的选择直接影响对象头 Mark Word 的锁状态变化 [citation:5][citation:13]:
| 锁对象类型 | Mark Word 初始状态 | 锁升级路径 |
|---|---|---|
普通 new Object() | 无锁(001) | 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 |
Class 对象 | 无锁(001) | 同上,但类对象通常长期存活,偏向锁收益低 |
| 已计算 hashCode 的对象 | 无锁(001),不可偏向 | 无锁 → 轻量级锁 → 重量级锁(跳过偏向锁) |
关键细节:
- 调用
hashCode()会占用 Mark Word 的 31 位空间,导致无法使用偏向锁(偏向锁需要存储线程 ID); - 如果锁对象在同步块内调用了
hashCode(),JVM 会撤销偏向锁,升级为轻量级锁 [citation:13]。
6. 面试官追问与高分回答模板
-
追问 1:"synchronized 的锁对象可以是什么?"
低分回答:"类对象、实例对象、任意对象。"(没有区分场景和陷阱)
高分回答:
-
追问 2:"为什么锁对象要用 final 修饰?"
高分回答:
-
追问 3:"用 String 作为锁对象有什么问题?"
高分回答:
-
追问 4:"synchronized(this) 和 synchronized 方法有什么区别?"
高分回答:
-
追问 5:"如何设计一个高并发的计数器,锁对象怎么选?"
高分回答:
-
追问 6:"锁对象调用 hashCode() 会影响 synchronized 吗?"
高分回答:
7. 方案选型速查表
| 场景 | 推荐锁对象 | 避坑要点 |
|---|---|---|
| 简单实例同步 | private final Object lock = new Object() | 不要用 this,防止外部竞争 |
| 静态数据同步 | private static final Object lock = new Object() | 不要用 Class 对象,防止反射竞争 |
| 类级方法同步 | synchronized(Xxx.class) | 注意与反射锁的冲突 |
| 按 ID 分锁 | Object[] locks 哈希分桶 | 桶数量要合理,避免哈希冲突 |
| 分段锁 | 每段独立的 Object 锁 | 段数 = 2 的幂次,方便位运算取模 |
| 读写分离 | ReentrantReadWriteLock | 不要用两个 synchronized 对象模拟 |
| 高并发计数 | LongAdder / AtomicInteger | 不要用 synchronized |
面试官想要的满分总结:
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~