Java并发编程:深入理解Intrinsic Lock机制
什么是Intrinsic Lock?
Intrinsic Lock(内部锁/监视器锁)是Java中每个对象都具备的一种基本同步机制。这种锁机制是Java语言内置的,不需要开发者显式创建,因此被称为"内部锁"。在并发编程中,Intrinsic Lock是实现线程安全的重要工具。
Synchronized关键字与Intrinsic Lock的关系
synchronized
关键字就是利用Intrinsic Lock来实现线程同步的。当一个线程进入synchronized代码块时,它会自动获取对象的Intrinsic Lock;当线程退出synchronized代码块时,无论是正常退出还是异常退出,锁都会被自动释放。
非线程安全的计数器示例
public class Counter {
private int count;
public int increase() {
return ++count; // 非线程安全操作
}
}
这个简单的计数器在多线程环境下会出现问题,因为++count
操作实际上包含三个步骤:
- 读取count当前值
- 修改count值
- 将新值写回count
在多线程环境下,这些步骤可能会被其他线程打断,导致数据不一致。
使用synchronized实现线程安全
public class Counter {
private Object lock = new Object(); // 任何对象都可以作为锁
private int count;
public int increase() {
synchronized(lock) { // 使用lock对象作为锁
return ++count;
}
}
}
synchronized块有三种常见使用方式:
- 使用任意对象作为锁(如上例)
- 使用当前对象作为锁(
synchronized(this) {...}
) - 将synchronized作为方法修饰符(
public synchronized int increase()
)
可重入性(Reentrancy)
Java的Intrinsic Lock是可重入的,这意味着一个线程可以重复获取它已经持有的锁。这种特性避免了线程因为等待自己已经持有的锁而导致的死锁。
public class Reentrancy {
public synchronized void a() {
System.out.println("a");
b(); // 可以调用另一个同步方法
}
public synchronized void b() {
System.out.println("b");
}
}
在这个例子中,当线程调用a()方法时获取了对象锁,然后在a()方法内部调用b()方法时,不需要再次获取锁(因为线程已经持有该锁),这就是可重入性的体现。
结构化锁(Structured Lock) vs 可重入锁(Reentrant Lock)
结构化锁
结构化锁是使用synchronized关键字实现的锁机制,它的特点是:
- 锁的获取和释放与代码块结构严格绑定
- 进入synchronized块时获取锁,退出时释放锁
- 锁的获取和释放顺序必须严格嵌套
例如:
synchronized(lockA) {
synchronized(lockB) {
// 代码
} // 先释放lockB
} // 后释放lockA
可重入锁(ReentrantLock)
与结构化锁不同,ReentrantLock提供了更灵活的锁机制:
- 可以手动控制锁的获取和释放
- 支持尝试获取锁、定时获取锁等高级功能
- 锁的获取和释放顺序可以更灵活
可见性(Visibility)问题
在多线程环境中,可见性是指一个线程对共享变量的修改能够及时被其他线程看到。Intrinsic Lock不仅提供互斥访问,还保证了可见性。
可见性问题主要由两个原因引起:
- 编译器和CPU的指令重排序优化
- CPU核心缓存与主内存不一致
Java内存模型(JMM)通过happens-before规则保证synchronized块的可见性:在释放锁之前的所有写操作对后续获取该锁的线程都是可见的。
最佳实践
- 锁对象选择:尽量使用私有final对象作为锁,避免使用可能被外部访问的对象
- 同步范围:同步块应尽可能小,只包含必要的代码
- 避免嵌套:尽量避免嵌套的synchronized块,以防死锁
- 考虑替代方案:对于复杂场景,考虑使用java.util.concurrent包中的高级并发工具
理解Intrinsic Lock机制是掌握Java并发编程的基础,合理使用这些机制可以构建出高效、线程安全的应用程序。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考