synchronized和ReentrantLock的区别
synchronized和ReentrantLock的区别
synchronized
和 ReentrantLock
都是 Java 中用于实现线程同步的机制,但它们在功能、使用方式、性能等方面存在显著区别。以下是两者的详细对比:
1. 基本概念
synchronized
是 Java 内置的关键字,用于实现线程同步,可以修饰方法或代码块。它通过 JVM 实现,属于隐式锁。ReentrantLock
是 Java 并发包(java.util.concurrent.locks
)中的一个类,属于显式锁,提供了比synchronized
更灵活的锁机制。
2. 主要区别
(1)使用方式
-
synchronized
-
代码块形式:
synchronized (lockObject) {// 同步代码块 }
-
方法形式:
public synchronized void method() {// 同步方法 }
-
-
ReentrantLock
-
需要显式创建锁对象,并在需要时手动加锁和解锁:
ReentrantLock lock = new ReentrantLock();lock.lock(); // 加锁 try {// 同步代码 } finally {lock.unlock(); // 解锁 }
-
(2)锁的获取与释放
synchronized
- 锁的获取和释放由 JVM 自动管理,无需手动干预。
- 如果发生异常,JVM 会自动释放锁。
ReentrantLock
- 必须手动调用
lock()
和unlock()
方法。 - 必须在
finally
块中释放锁,否则可能导致死锁。
- 必须手动调用
(3)公平性
-
synchronized
- 默认是非公平锁,无法直接控制锁的公平性。
-
ReentrantLock
-
可以选择公平锁或非公平锁:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁 ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁(默认)
-
(4)可中断性
-
synchronized
- 线程在等待锁时无法被中断,只能一直等待。
-
ReentrantLock
-
提供了
lockInterruptibly()
方法,允许线程在等待锁时响应中断:
javalock.lockInterruptibly(); // 可以被中断
-
(5)尝试获取锁
-
synchronized
- 没有直接的方法尝试获取锁,只能阻塞等待。
-
ReentrantLock
-
提供了
tryLock()
方法,可以尝试获取锁,如果获取失败则立即返回:
if (lock.tryLock()) {try {// 同步代码} finally {lock.unlock();} } else {// 获取锁失败的处理 }
-
(6)锁绑定多个条件
-
synchronized
- 只能使用
Object
的wait()
、notify()
和notifyAll()
方法,且所有线程共享同一个条件队列。
- 只能使用
-
ReentrantLock
-
可以绑定多个
Condition
对象,实现更精细的线程等待和唤醒:
Condition condition = lock.newCondition(); lock.lock(); try {condition.await(); // 线程等待condition.signal(); // 唤醒线程 } finally {lock.unlock(); }
-
(7)性能
synchronized
- 在 Java 6 之前性能较差,但在 Java 6 及之后版本中,JVM 对
synchronized
进行了大量优化(如锁粗化、锁消除、偏向锁、轻量级锁等),性能接近ReentrantLock
。
- 在 Java 6 之前性能较差,但在 Java 6 及之后版本中,JVM 对
ReentrantLock
- 在高竞争场景下,性能可能优于
synchronized
,但需要手动管理锁的获取和释放,增加了代码复杂度。
- 在高竞争场景下,性能可能优于
(8)可读性
synchronized
- 代码更简洁,易于理解和维护。
ReentrantLock
- 代码更复杂,需要手动管理锁的获取和释放,容易出错(如忘记释放锁)。
3. 使用场景
synchronized
- 适用于简单的同步需求,代码简洁,易于维护。
- 适合不需要复杂锁机制(如公平性、可中断性、多条件)的场景。
ReentrantLock
- 适用于需要更灵活的锁机制(如公平性、可中断性、多条件)的场景。
- 适合高竞争场景,或需要更精细控制线程同步的场景。
4. 总结对比表
特性 | synchronized | ReentrantLock |
---|---|---|
使用方式 | 关键字,隐式锁 | 类,显式锁 |
锁的获取与释放 | 自动管理 | 手动管理(lock() /unlock() ) |
公平性 | 默认非公平 | 可选择公平或非公平 |
可中断性 | 不支持 | 支持(lockInterruptibly() ) |
尝试获取锁 | 不支持 | 支持(tryLock() ) |
锁绑定多个条件 | 不支持 | 支持(Condition ) |
性能 | Java 6 后优化,接近 ReentrantLock | 高竞争场景下可能更优 |
可读性 | 代码简洁 | 代码复杂 |
5. 推荐使用
- 如果不需要复杂的锁机制,优先使用
synchronized
,代码更简洁且易于维护。 - 如果需要公平性、可中断性、多条件等高级功能,或在高竞争场景下需要更精细的控制,使用
ReentrantLock
。
6. 示例代码
synchronized
示例
public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}public static void main(String[] args) {SynchronizedExample example = new SynchronizedExample();Runnable task = () -> {for (int i = 0; i < 1000; i++) {example.increment();}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: " + example.count); // 输出 2000}
}
ReentrantLock
示例
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();Runnable task = () -> {for (int i = 0; i < 1000; i++) {example.increment();}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: " + example.count); // 输出 2000}
}
通过以上对比,可以根据具体需求选择合适的同步机制。