Java锁:synchronized 与 ReentrantLock 深入解析
在Java并发编程中,锁是确保线程安全的重要工具。本文将详细分析Java中的两种常见锁机制:synchronized关键字和ReentrantLock类,帮助开发者理解它们的工作原理、使用场景、优缺点以及如何在实际应用中进行选择。

synchronized 关键字
synchronized是Java内置的同步机制,用于在多线程环境中保护共享资源,确保同一时间只有一个线程可以执行被同步的方法或代码块。
工作原理
synchronized关键字可以用在方法或代码块上,分别称为同步方法和同步代码块。
-
同步方法:在方法声明中加上
synchronized关键字,表示该方法在执行时,当前对象(实例方法)或类(静态方法)将被锁定,其他线程在获得锁之前无法执行该方法。public synchronized void syncMethod() { // 线程安全的代码 } -
同步代码块:使用
synchronized关键字包裹特定代码块,可以更细粒度地控制同步,指定锁对象。public void syncBlock() { synchronized (this) { // 线程安全的代码 } }
优点
- 简洁易用:语法简单,直接使用关键字,易于理解和实现。
- 自动释放锁:JVM自动管理锁的获取和释放,不容易出现死锁。
缺点
- 性能开销:同步方法和代码块的性能开销较大,尤其在高并发环境中。
- 无法中断:线程在等待锁时无法中断,可能导致响应性问题。
- 锁粒度粗:对于同步方法,锁的粒度较粗,不适合需要细粒度控制的场景。
ReentrantLock 类
ReentrantLock是Java提供的显式锁,位于 java.util.concurrent.locks包中。它提供了更灵活的锁机制,可以显式地获取和释放锁。
工作原理
ReentrantLock的使用主要包括以下步骤:
- 创建锁对象:实例化
ReentrantLock对象。
ReentrantLock lock = new ReentrantLock();
- 获取锁:调用
lock.lock()方法获取锁。
lock.lock();
- 释放锁:在
finally块中调用lock.unlock()方法释放锁,确保锁的释放。
lock.lock();
try {
// 线程安全的代码
} finally {
lock.unlock();
}
优点
- 高性能:相比
synchronized,ReentrantLock在高并发环境下性能更好。 - 可中断锁获取:
lock.lockInterruptibly()方法允许在等待锁时响应中断。 - 尝试获取锁:
tryLock()方法可以尝试获取锁,不阻塞线程。 - 公平锁:可以设置为公平锁,按照请求锁的顺序分配锁,避免线程饥饿。
- 灵活性高:支持更复杂的同步需求,如多条件变量。
缺点
- 代码复杂:显式的锁管理增加了代码的复杂性,需要手动获取和释放锁。
- 易出错:需要确保在
finally块中释放锁,否则可能导致死锁。 - 潜在的死锁风险:如果没有正确管理锁的释放,容易出现死锁问题。
比较分析
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 使用简便性 | 语法简单,易于使用 | 需要显式获取和释放锁,代码复杂度高 |
| 性能 | 较高的性能开销 | 高并发环境下性能更好 |
| 锁释放管理 | JVM自动管理 | 需手动管理,易出现错误 |
| 中断响应 | 不支持 | 支持,通过 lockInterruptibly()实现 |
| 公平锁 | 不支持 | 支持,通过构造函数设置 |
| 条件变量 | 不支持 | 支持,通过 newCondition()实现 |
| 重入性 | 支持 | 支持 |
| 锁粒度控制 | 适合简单同步场景 | 适合复杂的同步需求 |
选择建议
- 简单场景:如果只是简单地保护共享资源,且锁粒度要求不高,
synchronized是更好的选择,因其使用简便,错误率低。 - 高并发场景:在高并发或需要复杂同步控制的场景下,
ReentrantLock提供了更高的性能和灵活性,适合需要中断响应、公平锁或多条件变量的情况。
使用示例
synchronized 示例
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(example::increment);
Thread t2 = new Thread(example::increment);
t1.start();
t2.start();
}
}
ReentrantLock 示例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(example::increment);
Thread t2 = new Thread(example::increment);
t1.start();
t2.start();
}
}
结论
在Java并发编程中,synchronized关键字和ReentrantLock类都是实现线程安全的重要工具。synchronized适用于简单同步场景,使用方便且错误率低,而ReentrantLock则在高并发和复杂同步需求的场景中表现更佳,提供了更高的性能和灵活性。
通过了解它们的工作原理、优缺点及实际应用场景,开发者可以根据具体需求选择合适的锁机制,从而实现高效、安全的并发编程。无论选择哪种锁机制,都应注意避免死锁,并正确管理锁的获取和释放,以确保程序的稳定性和可靠性。


