synchronized 关键字是否实现了一种重入锁?请解释其实现原理。
参考回答**
是的,synchronized
关键字实现了一种重入锁。重入锁意味着同一个线程可以多次获取同一个锁,而不会导致死锁。
解释:
- 重入:当一个线程已经持有某个对象的锁时,它可以再次请求该锁而不会被阻塞。这意味着同一线程可以多次进入同步代码块,而不需要等待自己释放锁。
- 实现原理:
synchronized
是通过 内置的监视器锁(monitor lock) 来实现的。每个对象都有一个与之关联的监视器锁,当线程进入一个被synchronized
修饰的代码块时,首先尝试获取该锁。如果该锁已经被当前线程占有,线程就可以继续进入同步代码块,而不被阻塞。
详细讲解与拓展
1. 重入锁的实现原理
Java 中的 synchronized
是基于对象的监视器锁(monitor lock)实现的。每个对象都隐式地关联一个监视器锁。通过 synchronized
修饰的代码块或者方法,会使用该对象的监视器锁来保证同步性。
在实现重入锁时,synchronized
利用了一个线程的 锁计数器 来实现。每当线程获取锁时,锁计数器就会增加。当线程释放锁时,锁计数器减少,直到计数器为 0 时,锁才会被其他线程获取。
工作过程:
- 初次获取锁:当一个线程第一次访问同步代码块时,它会尝试获取锁。如果锁未被其他线程占用,线程会成功获取该锁,进入同步代码块,并将锁计数器设置为 1。
- 重入锁:如果线程在已持有该锁的情况下再次进入同步代码块,锁计数器将增加。例如,如果同一线程再次请求锁,计数器增加到 2。此时,线程可以继续执行同步代码块。
- 释放锁:当线程退出同步代码块时,锁计数器减少。如果锁计数器为 0,表示锁已被完全释放,其他线程可以获取该锁。
2. 代码示例:重入锁
public class ReentrantLockExample {
private synchronized void method1() {
System.out.println("Method 1 entered.");
method2(); // 在方法 1 中调用方法 2
}
private synchronized void method2() {
System.out.println("Method 2 entered.");
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
example.method1(); // 调用 method1,自动调用 method2
}
}
输出:
Method 1 entered.
Method 2 entered.
分析:
method1()
和method2()
都是同步方法,它们会尝试获取同一个锁。- 当
method1()
被调用时,它会成功获取锁并进入方法体。然后在method1()
中调用method2()
,由于线程已经持有该锁,method2()
可以再次成功获取同一个锁并执行。
这就是重入锁的特性:同一线程可以多次获取同一个锁,而不会阻塞自己。
3. synchronized
的底层实现
synchronized
的实现依赖于 monitor(监视器锁),在 JVM 中每个对象都有一个监视器。JVM 对象锁的底层实现是通过 对象头 和 对象监视器 来管理的。
- 对象头:每个对象的对象头中存储了对象的锁信息,包括锁的状态和持有锁的线程信息。
- 对象监视器:对象监视器是一个与每个对象相关联的内核级别的锁,它负责管理线程对该对象锁的获取和释放。
当一个线程请求同步代码块时,JVM 会检查该对象的监视器,查看该线程是否已经持有锁。如果该线程已经持有锁,锁计数器将增加(即重入)。如果线程没有持有锁,JVM 会将该线程添加到锁的等待队列,直到锁可用。
4. 重入锁的优势与局限
- 优势:
- 避免死锁:由于同一线程可以多次获取锁,避免了因为自己等待自己释放锁导致的死锁问题。
- 递归调用支持:当线程在方法内部递归调用同步方法时,不需要担心锁被阻塞,简化了递归算法的设计。
- 局限:
- 性能开销:每次获取锁时都需要操作锁计数器,虽然开销相对较小,但频繁的锁获取和释放可能影响性能。
- 锁竞争:虽然
synchronized
支持重入,但当多个线程竞争同一锁时,仍然会导致性能瓶颈。
5. 相关拓展:重入锁的其他实现
Java 还提供了显式的 重入锁 实现,如 ReentrantLock
类。ReentrantLock
类与 synchronized
类似,但提供了更细粒度的控制和更强大的功能(如尝试获取锁、定时锁等)。
ReentrantLock 示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void method1() {
lock.lock(); // 获取锁
try {
System.out.println("Method 1 entered.");
method2();
} finally {
lock.unlock(); // 释放锁
}
}
public void method2() {
lock.lock();
try {
System.out.println("Method 2 entered.");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
example.method1(); // 调用 method1,自动调用 method2
}
}
优势:
- 可以尝试获取锁(
tryLock()
)。 - 支持定时获取锁(
lock(long timeout)
)。 - 支持中断的锁获取(
lockInterruptibly()
)。
总结
synchronized
实现了重入锁,允许同一线程多次进入同步代码块。- 其原理是通过锁计数器和监视器机制实现的,当线程获取锁时,计数器增加,释放锁时,计数器减少,直到为 0 才释放锁。
- 优势:避免了死锁和递归调用的阻塞,简化了编程。
- 局限:性能可能受到一定影响,尤其是高竞争的情况下。