ReentrantLock和
synchronized`都是Java中用于实现线程同步的机制,但它们在多个方面存在显著的区别:
1. 实现方式
-
synchronized:
- 是Java语言的关键字,属于原生语法层面的互斥锁。
- 由JVM实现,不需要显式地获取和释放锁。
-
ReentrantLock:
- 是JDK 1.5之后提供的API层面的互斥锁。
- 需要通过
lock()
方法显式地获取锁,并通过unlock()
方法显式地释放锁。
2. 锁的获取和释放
-
synchronized:
- 锁的获取和释放是隐式的,编译器会自动在同步块的前后插入
monitorenter
和monitorexit
字节码指令。 - 如果获取锁失败,线程会进入阻塞状态,直到锁被释放。
- 锁的获取和释放是隐式的,编译器会自动在同步块的前后插入
-
ReentrantLock:
- 锁的获取和释放是显式的,需要手动调用
lock()
和unlock()
方法。 - 为了避免忘记释放锁导致死锁,通常建议在
finally
块中调用unlock()
方法。
- 锁的获取和释放是显式的,需要手动调用
3. 锁的公平性
-
synchronized:
- 默认情况下是不公平的,即线程获取锁的顺序是不确定的。
-
ReentrantLock:
- 可以选择公平锁或非公平锁。通过构造函数参数指定是否使用公平锁。
- 公平锁会按照线程请求锁的顺序来分配锁,但性能相对较低。
4. 锁的粒度
-
synchronized:
- 锁的粒度较粗,一个同步块只能锁定一个对象。
-
ReentrantLock:
- 锁的粒度较细,可以通过
tryLock()
方法尝试获取锁,如果获取失败可以进行其他操作。 - 支持多个条件变量(
Condition
),可以在不同的条件下等待和通知线程。
- 锁的粒度较细,可以通过
5. 性能
-
synchronized:
- 在Java 6之后,
synchronized
进行了大量优化,包括偏向锁、轻量级锁和自旋锁,性能得到了显著提升。 - 在大多数情况下,
synchronized
的性能已经足够好。
- 在Java 6之后,
-
ReentrantLock:
- 提供了更多的灵活性和高级功能,如可中断锁、超时锁等。
- 在某些特定场景下,
ReentrantLock
的性能可能优于synchronized
,特别是在需要细粒度控制和高级功能的场景下。
6. 可重入性
-
synchronized:
- 是可重入的,即一个线程可以多次获取同一个锁而不会导致死锁。
-
ReentrantLock:
- 也是可重入的,通过内部计数器来记录线程获取锁的次数。
示例代码
synchronized示例
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
ReentrantLock示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
总结
- synchronized:简单易用,适用于大多数场景,性能较好。
- ReentrantLock:灵活性高,适用于需要细粒度控制和高级功能的场景。
选择哪种同步机制取决于具体的应用场景和需求。