在多线程并发开发中,线程间同步控制是重中之重。Java 语言为我们提供了多种锁机制,其中最常用的两种就是内置的 synchronized
关键字和 JDK 1.5 之后引入的 ReentrantLock
。这两者虽然都能实现线程间的互斥与同步,但实现原理和使用方式却有所差异。那么它们之间究竟有哪些区别?开发时又该如何选择?本文将详细对比讲解。
一、synchronized 概述
synchronized
是 Java 的内置同步机制,占用 JVM 原语级别的支持。可以作用于方法或代码块,实现对对象/类的加锁,保证同一时间只有一个线程能访问被锁定的代码区域。
基本用法:
synchronized (lockObj) {
// 临界区
}
或者:
public synchronized void foo() {
// 同步方法
}
二、ReentrantLock 概述
ReentrantLock
是 JDK 1.5 提供的 Lock 接口实现,可重入互斥锁。其设计思想上与 synchronized 类似,但功能更灵活,扩展性更强,由 Java 代码实现(位于 java.util.concurrent.locks
包中)。
基本用法:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
三、二者的主要区别
1. 底层实现
- synchronized:依赖于 JVM 层实现,由 JVMmonitor monitorenter/monitorexit 指令维护锁的获取与释放。属于 JVM 内置锁。
- ReentrantLock:完全由 Java 代码实现(基于AQS AbstractQueuedSynchronizer),可扩展。
2. 可重入性
两者均为可重入锁。同一线程可多次获取同一把锁。
3. 功能特性
特性 | synchronized | ReentrantLock |
---|---|---|
可重入 | √ | √ |
公平锁 | × | √(可指定公平/非公平) |
支持中断 | × | √(lockInterruptibly) |
超时尝试加锁 | × | √(tryLock) |
条件变量 | × | √(Condition实现等待通知) |
手动释放锁 | ×(自动) | √(需 unlock() ) |
多锁支持 | × | √ |
死锁检测 | × | × |
具体说明:
-
公平锁与非公平锁
- ReentrantLock 可通过构造方法指定为“公平锁”,即先进先出获得锁。synchronized 总是非公平的。
-
可响应中断
- ReentrantLock 支持可中断加锁(
lockInterruptibly
),即等待锁期间可以响应 interrupt 中断。 - synchronized 无法响应中断,只能一直阻塞。
- ReentrantLock 支持可中断加锁(
-
支持超时尝试加锁
- ReentrantLock 的
tryLock(long timeout, TimeUnit unit)
支持超时等待。 - synchronized 不支持超时。
- ReentrantLock 的
-
支持多个条件变量
- ReentrantLock 可创建多个 Condition,实现细粒度的 notify/wait 机制。
- synchronized 只支持一个 monitor(wait/notifyAll)。
-
灵活释放锁
- ReentrantLock 必须手动 unlock,容易遗漏(需配合 try-finally),但也更灵活。
- synchronized 自动释放锁(出临界区自动释放)。
4. 性能表现
- JDK1.6后,synchronized 经过锁优化(偏向锁、轻量级锁、自旋锁等),性能已大幅提升,无明显劣势。
- ReentrantLock 适合需要特殊锁特性(如公平性、可中断、条件变量等)的场景。
四、核心区别总结
核心区别一句话总结:
synchronized
是JVM层的“内置”锁,简单易用,不能灵活控制锁的高级特性;而 ReentrantLock
是Java代码实现的高级可重入锁,具有更多的配置和控制能力。
- 【自动 vs 手动释放】—— synchronized 自动,ReentrantLock 需手动 unlock
- 【功能维度】—— ReentrantLock 支持公平性、可中断性、超时和条件变量,synchronized 不支持
- 【适用场景】—— 一般简单场景下用 synchronized,复杂并发控制下用 ReentrantLock
五、实际开发如何选择?
-
简单同步互斥:
优先用 synchronized,语法简单,容易维护。 -
需要可中断/超时/公平/多条件变量等高级特性:
选 ReentrantLock。 -
避免忘记释放锁出现死锁问题:
synchronized 更保险(出错小)。 -
性能角度:
JDK1.6 之后两者性能差异不大,大部分场景可优先 synchronized。
六、代码简单对比
synchronized:
public void syncMethod() {
synchronized(this){
// critical section
}
}
ReentrantLock:
ReentrantLock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try{
// critical section
} finally{
lock.unlock();
}
}