1. 对象锁与类锁的基本概念
在多线程编程中,锁机制是保证线程安全的重要手段。Java提供了两种基本的锁机制:对象锁和类锁。这两种锁虽然都用于同步控制,但它们的应用场景和作用范围有着本质区别。
对象锁是针对特定对象实例的同步控制机制。当一个线程获取了某个对象的对象锁后,其他尝试获取该对象锁的线程将被阻塞,直到锁被释放。对象锁的实现通常通过synchronized实例方法或synchronized代码块(使用this作为锁对象)来完成。
类锁则是针对整个类的同步控制机制。它作用于类的Class对象上,而不是类的实例。类锁可以通过synchronized静态方法或synchronized代码块(使用Class对象作为锁对象)来实现。类锁会阻止多个线程同时访问类的所有同步静态方法。
2. 对象锁的深入解析
2.1 对象锁的实现方式
对象锁主要有两种实现形式:
- 同步实例方法:在方法声明中添加synchronized关键字
java复制public synchronized void method() {
// 方法体
}
- 同步代码块:使用this作为锁对象
java复制public void method() {
synchronized(this) {
// 同步代码块
}
}
这两种形式本质上是等价的,都是基于当前对象实例(this)作为锁对象。当一个线程进入同步方法或同步代码块时,它会获取该对象实例的锁,其他线程必须等待锁释放后才能访问。
2.2 对象锁的特性
对象锁具有以下几个重要特性:
- 实例级别:每个对象实例都有自己独立的锁,不同实例的锁互不干扰
- 可重入性:同一个线程可以多次获取同一个锁(避免死锁)
- 互斥性:同一时间只有一个线程能持有该锁
- 可见性保证:锁的释放会强制将工作内存中的修改刷新到主内存
注意:对象锁只对同步方法或同步代码块有效,非同步方法不受锁的限制,即使锁被持有,其他线程仍可调用非同步方法。
3. 类锁的深入解析
3.1 类锁的实现方式
类锁也有两种主要实现形式:
- 同步静态方法:
java复制public static synchronized void staticMethod() {
// 方法体
}
- 同步代码块:使用Class对象作为锁
java复制public void method() {
synchronized(MyClass.class) {
// 同步代码块
}
}
类锁是基于类的Class对象实现的。每个类在JVM中只有一个Class对象,因此类锁是全局唯一的,对所有实例都有效。
3.2 类锁的特性
类锁具有以下关键特性:
- 全局唯一性:一个类只有一个Class对象,因此类锁也是唯一的
- 独立于实例:类锁与对象锁互不干扰,可以同时被不同线程获取
- 影响所有实例:类锁会阻塞所有试图访问同步静态方法的线程
- 同样具备可重入性:同一个线程可以多次获取同一个类锁
4. 对象锁与类锁的关键区别
4.1 作用范围对比
| 特性 | 对象锁 | 类锁 |
|---|---|---|
| 作用对象 | 对象实例 | 类的Class对象 |
| 影响范围 | 单个实例 | 所有实例 |
| 锁对象 | this | Class对象 |
| 同步方法 | 实例方法 | 静态方法 |
4.2 使用场景差异
对象锁适用场景:
- 保护实例变量的访问
- 保证实例方法的线程安全
- 需要对不同实例进行独立同步控制的场景
类锁适用场景:
- 保护静态变量的访问
- 保证静态方法的线程安全
- 需要全局同步控制的场景
- 单例模式的实现
4.3 性能考量
对象锁通常比类锁具有更好的并发性能,因为:
- 不同实例的对象锁互不干扰,允许真正的并行操作
- 类锁会成为系统瓶颈,特别是在高并发场景下
- 过度使用类锁会导致不必要的线程阻塞
5. 实际应用中的注意事项
5.1 避免死锁的策略
无论是对象锁还是类锁,都需要注意死锁问题。常见预防措施包括:
- 按固定顺序获取多个锁
- 使用tryLock()设置超时时间
- 避免在持有锁时调用外部方法
- 尽量减少同步代码块的范围
5.2 锁的选择建议
在实际开发中,应根据具体需求选择合适的锁机制:
- 优先考虑对象锁,除非确实需要全局同步
- 对于静态数据的访问,必须使用类锁
- 考虑使用更高级的并发工具(如ReentrantLock)替代synchronized
- 在JDK8+中,可以考虑使用StampedLock提高读多写少场景的性能
5.3 常见误区与陷阱
- 错误认为synchronized方法都是类锁:只有静态方法上的synchronized才是类锁
- 忽略锁的可重入性:同一线程多次获取同一锁不会死锁,但要确保释放次数匹配
- 过度同步:同步范围过大或锁粒度太粗会降低性能
- 混淆锁对象:确保同步代码块使用的锁对象与预期一致
6. 高级话题:锁的优化技术
6.1 锁粗化与锁消除
JVM会对锁进行一些优化:
- 锁粗化:将多个连续的锁操作合并为一个
- 锁消除:通过逃逸分析移除不必要的锁
- 偏向锁:优化无竞争情况下的锁性能
- 自适应自旋:根据历史数据动态调整自旋次数
6.2 替代方案
除了synchronized,Java还提供了其他同步机制:
- ReentrantLock:更灵活的锁实现
- ReadWriteLock:读写分离锁
- StampedLock:乐观读锁
- 原子变量:CAS操作的原子类
在实际项目中,我经常遇到开发人员混淆这两种锁的情况。一个典型的错误是在需要对象锁的地方使用了类锁,导致性能下降;或者在需要全局同步的地方错误地使用了对象锁,造成线程安全问题。理解它们的本质区别对于编写正确、高效的多线程代码至关重要。
