深入理解Java中的重入锁(ReentrantLock)和synchronized的区别及重入锁的公平性特性
一、引言
让我们深入理解Java中的重入锁(ReentrantLock)和synchronized的区别,以及重入锁的公平性特性。
二、重入锁概述
重入锁和synchronized都是可重入的,这意味着同一个线程可以多次获取同一个锁而不会死锁。这是两者的共同点,但它们在实现和特性上有很大的区别。
(一)主要区别
- 锁的获取和释放方式
- synchronized:是隐式的,由JVM控制。
synchronized void method() {
// 进入方法时自动获取锁
doSomething();
// 方法结束或异常时自动释放锁
}
- **ReentrantLock**:是显式的,需要手动控制。
private ReentrantLock lock = new ReentrantLock();
void method() {
lock.lock(); // 手动获取锁
try {
doSomething();
} finally {
lock.unlock(); // 手动释放锁,一定要在finally中释放
}
}
- 功能特性
- ReentrantLock提供了更多的高级功能:
- 可以实现公平锁:
- ReentrantLock提供了更多的高级功能:
// 创建公平锁
private ReentrantLock fairLock = new ReentrantLock(true);
void fairMethod() {
fairLock.lock();
try {
// 多个线程按照请求锁的顺序获得锁
processData();
} finally {
fairLock.unlock();
}
}
- **可以尝试获取锁**:
private ReentrantLock lock = new ReentrantLock();
void tryLockMethod() {
// 尝试在2秒内获取锁
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 获取到锁后的操作
processData();
} finally {
lock.unlock();
}
} else {
// 无法获取锁时的替代处理
handleAlternative();
}
}
(二)实际案例理解使用场景
假设我们有一个共享的缓存系统:
public class CacheSystem {
private Map<String, String> cache = new HashMap<>();
// 使用synchronized的简单实现
public synchronized String getSimple(String key) {
return cache.get(key);
}
// 使用ReentrantLock的高级实现
private ReentrantLock lock = new ReentrantLock(true); // 公平锁
public String getAdvanced(String key) {
lock.lock();
try {
String value = cache.get(key);
if (value == null) {
// 模拟从数据库读取数据的耗时操作
value = loadFromDatabase(key);
cache.put(key, value);
}
return value;
} finally {
lock.unlock();
}
}
public String getTryLock(String key) {
try {
// 尝试获取锁,如果1秒内获取不到就返回缓存过期的标记
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
return cache.get(key);
} finally {
lock.unlock();
}
} else {
return "CACHE_BUSY";
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "INTERRUPTED";
}
}
}
三、使用建议
- 使用synchronized的场景:
- 代码块比较简单,不需要高级特性。
- 不需要尝试获取锁或设置超时。
- 不需要公平性保证。
- 希望代码更简洁,减少出错可能。
- 使用ReentrantLock的场景:
- 需要公平性保证,如排队系统。
- 需要尝试获取锁或设置超时,如限时操作。
- 需要可中断的锁获取操作。
- 需要关联多个条件变量(Condition)。
- 需要知道锁的状态(是否被持有、等待线程数等)。
- 使用公平锁的场景:
- 系统对反应时间的公平性要求较高。
- 线程优先级需要按照先来先服务的原则。
- 需要避免线程饥饿问题。
四、注意事项
需要注意的是,公平锁会带来性能开销,因为它需要维护一个有序队列。在一般情况下,非公平锁的性能会更好,因为它允许线程竞争,减少了线程切换的开销。
希望这个详细的解释和示例能帮助你更好地理解这些概念,并在实际开发中做出正确的选择。如果你有任何具体的使用场景需要讨论,欢迎评论区讨论。

1004

被折叠的 条评论
为什么被折叠?



