在Java多线程编程中,同步机制是确保线程安全的核心。synchronized和ReentrantLock是Java中最常用的两种同步工具,它们各有特点,适用于不同的场景。本文将深入分析这两种同步机制的实现原理、使用方式、性能差异以及适用场景,帮助开发者做出更明智的选择。
synchronized是Java内置的同步机制,它提供了一种简单的方式来实现线程同步:
public class SynchronizedExample {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public void incrementBlock() {
// 同步代码块
synchronized(this) {
count++;
}
}
}ReentrantLock是Java 5引入的显式锁机制,位于java.util.concurrent.locks包中:
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(); // 释放锁
}
}
}两者都是可重入的,即同一个线程可以多次获取同一个锁:
// synchronized的可重入性
public synchronized void methodA() {
methodB(); // 可以调用另一个同步方法
}
public synchronized void methodB() {
// ...
}
// ReentrantLock的可重入性
public void methodA() {
lock.lock();
try {
methodB();
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
}synchronized不提供公平性保证,而ReentrantLock可以配置为公平锁:
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);公平锁会按照线程请求锁的顺序来分配锁,但会降低吞吐量。
synchronized:自动获取和释放锁,进入同步代码块时获取,退出时释放ReentrantLock:需要显式调用lock()和unlock()方法ReentrantLock可以响应中断,而synchronized不能:
public void interruptibleLock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可中断的获取锁
try {
// 临界区代码
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 处理中断
}
}ReentrantLock可以创建多个Condition对象,而synchronized只能有一个等待队列:
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Object[] items = new Object[100];
private int count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待不满条件
items[count++] = x;
notEmpty.signal(); // 唤醒等待不空条件的线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待不空条件
Object x = items[--count];
notFull.signal(); // 唤醒等待不满条件的线程
return x;
} finally {
lock.unlock();
}
}
}在Java早期版本中,ReentrantLock的性能明显优于synchronized。但随着Java版本的更新,synchronized的性能得到了显著提升,现在两者的性能差异已经不大。
在低竞争情况下,synchronized可能更优,因为它是JVM内置特性;在高竞争情况下,ReentrantLock的可配置性可能带来更好的性能。
public void tryLockExample() {
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// 获取锁成功,执行临界区代码
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他操作
}
}public void transfer(Account from, Account to, int amount) {
// 按照固定顺序获取锁,避免死锁
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if (fromHash < toHash) {
from.lock.lock();
to.lock.lock();
} else if (fromHash > toHash) {
to.lock.lock();
from.lock.lock();
} else {
// 哈希冲突时的处理
synchronized (from) {
synchronized (to) {
// 转账操作
}
}
return;
}
try {
// 执行转账操作
} finally {
from.lock.unlock();
to.lock.unlock();
}
}synchronized是基于JVM层面的Monitor实现的,包含以下概念:
在字节码层面,同步方法通过ACC_SYNCHRONIZED标志实现,同步代码块通过monitorenter和monitorexit指令实现。
ReentrantLock是基于AQS(AbstractQueuedSynchronizer)实现的,主要包含:
在选择synchronized和ReentrantLock时,可以考虑以下因素:
synchronized,复杂场景用ReentrantLockReentrantLocksynchronized代码更简洁,不易出错ReentrantLocksynchronized和ReentrantLock都是Java中强大的同步工具,各有优缺点:
特性 | synchronized | ReentrantLock |
|---|---|---|
实现方式 | JVM内置 | JDK实现 |
使用复杂度 | 简单 | 较复杂 |
可重入性 | 支持 | 支持 |
公平性 | 不支持 | 可配置 |
可中断性 | 不支持 | 支持 |
尝试获取锁 | 不支持 | 支持(tryLock) |
条件变量 | 单一 | 多个 |
性能 | 现代JVM优化好 | 高竞争下可能更优 |
异常时自动释放锁 | 支持 | 需手动释放 |
在实际开发中,应根据具体需求选择合适的同步机制。对于大多数简单场景,synchronized是更简洁安全的选择;而对于需要更高级功能的复杂场景,ReentrantLock提供了更大的灵活性。
理解这两种同步机制的原理和特性,有助于我们编写出更高效、更安全的并发程序。