Java中的锁机制详解—— synchronized与ReentrantLock

更新时间:2024-04-26 14:40:28   人气:9944
在深入探讨Java并发编程的核心技术时,我们无法绕过其关键的线程同步工具:synchronized关键字和JUC库提供的可重入锁(ReentrantLock)。这两种机制均用于控制多线程环境下的资源访问顺序以及保护共享数据的一致性。

**一、Synchronized**

`synchronized`是Java内置的关键字,它提供了原子性和可见性的保障。通过将一段代码块或者方法声明为`synchronized`,可以确保同一时间只有一个线程能够执行该段被锁定的代码区域:

java

public class SynchronizedExample {
private int count = 0;

public synchronized void increment() { // 方法级锁
count++;
}

public void threadSafeIncrement() {
synchronized (this) { // 同步代码块方式获取对象锁
count++;
}
}
}

在这两种形式中,当一个线程进入临界区后,其他试图获得相同监视器锁的所有线程都将被迫等待。一旦持有锁的线程退出`synchronized`代码块或方法,则会释放锁,并唤醒其中一个等待的线程来竞争并可能获得这把锁。

然而,基于`synchronized`实现的锁具有一定的局限性:
1. **不可中断**: 获取不到锁的线程只能无期限地阻塞在那里。
2. **不公平策略**: JVM并不保证公平调度,即先请求锁的线程未必能优先得到锁。
3. **缺乏更细粒度的操作**: 只能在类级别或者是实例级别的monitor上进行加解锁操作。

**二、ReentrantLock**

为了克服上述限制,Java引入了 java.util.concurrent.locks.ReentrantLock 类作为显式 Lock 对象的一个例子。ReentrantLock 是一种更为灵活且功能丰富的互斥锁。

以下是一个使用 ReentrantLock 的示例:

java

import java.util.concurrent.locks.*;

class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increase() {
lock.lock(); // 显式的lock()

try {
count++;
} finally {
lock.unlock(); // 必须放在finally以防止死锁
}
}

// 更进一步,还可以指定是否需要公平锁
public ReentrantLockDemo(boolean fair){
this(new ReentrantLock(fair));
}
}



相比 `synchronized` ,ReentrantLock 提供了额外的功能特性:
1. **可响应中断:** 线程可以通过调用 interrupt 来打断正在尝试获取但尚未成功拿到锁的其它线程。
2. **支持公平锁/非公平锁选择:** 构造函数接受布尔参数决定新创建的是公平还是非公平锁,默认是非公平锁;而 synchronized 始终采用的是非公平锁。
3. ** Condition 接口的支持:** 允许程序更加精细地对多个条件队列及相关的 await/signal 操作做出处理,在复杂场景下更具优势。

综述而言,尽管 synchronize 和 ReentrantLock 都是用来解决 Java 中的数据一致性问题,但在灵活性和功能性方面后者显然有更多选项可供程序员针对不同需求精确定制解决方案。不过,鉴于 synchronized 在简洁性和易用性上的优点,对于简单情况仍不失为一个好的选择。开发者需结合实际应用场景权衡利弊,合理选用适合自己的锁机制。