synchronized vs Lock 对比
没有最好的,只有最适合的。
核心区别
| 对比项 | synchronized | Lock (ReentrantLock) |
|---|---|---|
| 获取/释放 | 自动 | 手动 |
| 公平性 | 非公平 | 可配置 |
| 超时等待 | ❌ | ✅ tryLock(timeout) |
| 中断等待 | ❌ | ✅ lockInterruptibly() |
| 条件变量 | 一个(Object) | 多个(Condition) |
| 性能 | JDK 6+ 已优化 | 相当或更好 |
场景一:简单互斥
java
// ✅ synchronized:简单场景首选
public class SimpleCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
// ✅ ReentrantLock:功能更多
public class SimpleCounterWithLock {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}建议:简单互斥用 synchronized,代码更简洁。
场景二:避免死锁
java
// ❌ synchronized:可能死锁
public void transfer(Account a, Account b, int amount) {
synchronized (a) {
synchronized (b) {
// 转账
}
}
}
// ✅ ReentrantLock + tryLock:避免死锁
public boolean transfer(Account a, Account b, int amount) {
// 固定顺序获取锁
if (a.id < b.id) {
if (!a.lock.tryLock(1, TimeUnit.SECONDS)) return false;
} else {
if (!b.lock.tryLock(1, TimeUnit.SECONDS)) return false;
}
try {
// 转账
return true;
} finally {
if (a.id < b.id) a.lock.unlock();
else b.lock.unlock();
}
}建议:多锁场景用 tryLock 避免死锁。
场景三:可中断等待
java
// ❌ synchronized:不支持中断
public synchronized void process() throws InterruptedException {
while (condition) {
wait(); // 不响应中断
}
}
// ✅ ReentrantLock:可中断
public void process() throws InterruptedException {
lock.lockInterruptibly();
try {
while (!conditionMet) {
condition.await(); // 可被中断
}
} finally {
lock.unlock();
}
}建议:需要响应中断时用 ReentrantLock。
场景四:多个条件变量
java
// ❌ synchronized:只有一个条件
public class SingleCondition {
private final Object lock = new Object();
private boolean notEmpty = false;
private boolean notFull = false;
public void put(Object item) throws InterruptedException {
synchronized (lock) {
while (notFull) { // 只能用 while
lock.wait();
}
// 生产
notEmpty = true;
lock.notifyAll();
}
}
public Object take() throws InterruptedException {
synchronized (lock) {
while (!notEmpty) { // 没法区分「不等」和「不等满」
lock.wait();
}
// 消费
notFull = true;
lock.notifyAll();
}
}
}
// ✅ ReentrantLock:多个条件
public class MultiCondition {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (isFull()) {
notFull.await(); // 只等「不满」的条件
}
// 生产
notEmpty.signal(); // 只通知「非空」等待的线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (isEmpty()) {
notEmpty.await(); // 只等「非空」的条件
}
// 消费
notFull.signal(); // 只通知「不满」等待的线程
} finally {
lock.unlock();
}
}
}建议:需要多个等待条件时用 Condition。
场景五:读多写少
java
// ❌ synchronized:读写都互斥
private int value = 0;
public synchronized int read() { return value; }
public synchronized void write(int v) { value = v; }
// ✅ ReadWriteLock:读并发,写独占
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private int value = 0;
public int read() {
rwLock.readLock().lock();
try {
return value;
} finally {
rwLock.readLock().unlock();
}
}
public void write(int v) {
rwLock.writeLock().lock();
try {
value = v;
} finally {
rwLock.writeLock().unlock();
}
}建议:读多写少用 ReadWriteLock。
性能对比
java
public class PerformanceTest {
private static final int THREADS = 8;
private static final int ITERATIONS = 10_000_000;
// synchronized 版本
private int counter1 = 0;
private final Object lock1 = new Object();
// ReentrantLock 版本
private int counter2 = 0;
private final ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws Exception {
PerformanceTest test = new PerformanceTest();
// 测试 synchronized
long start = System.nanoTime();
test.testSync();
long syncTime = System.nanoTime() - start;
// 测试 ReentrantLock
start = System.nanoTime();
test.testLock();
long lockTime = System.nanoTime() - start;
System.out.println("synchronized: " + syncTime / 1_000_000 + " ms");
System.out.println("ReentrantLock: " + lockTime / 1_000_000 + " ms");
System.out.println("差异: " + String.format("%.1f%%", (lockTime - syncTime) * 100.0 / syncTime));
}
}典型结果(JDK 17, 8 核):
synchronized: 120 ms
ReentrantLock: 125 ms
差异: 4.2%结论:现代 JDK 下,两者性能差异不大。
选择决策树
需要同步?
│
├─ 需要 tryLock / 超时? ────→ ReentrantLock
│
├─ 需要可中断? ──────────────→ ReentrantLock
│
├─ 需要多个条件变量? ─────────→ ReentrantLock
│
├─ 读多写少? ────────────────→ ReentrantReadWriteLock
│
└─ 以上都不需要? ────────────→ synchronized(首选)最佳实践
优先用 synchronized
java
// ✅ 推荐:简单互斥
public class Service {
private int count = 0;
public synchronized void increment() {
count++;
}
}复杂场景用 Lock
java
// ✅ 推荐:需要高级特性
public class AdvancedService {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public void awaitWithTimeout() throws InterruptedException {
lock.lockInterruptibly();
try {
if (!conditionMet) {
notEmpty.await(5, TimeUnit.SECONDS);
}
} finally {
lock.unlock();
}
}
}永远在 finally 中释放 Lock
java
// ❌ 危险
lock.lock();
try {
// 可能抛异常
doSomething();
} catch (Exception e) {
// 出错了,但锁没释放
}
// ✅ 安全
lock.lock();
try {
doSomething();
} finally {
lock.unlock(); // 一定释放
}总结
| 场景 | 推荐 |
|---|---|
| 简单互斥 | synchronized |
| 多锁 + 避免死锁 | ReentrantLock + tryLock |
| 可中断等待 | ReentrantLock |
| 多个条件变量 | ReentrantLock + Condition |
| 读多写少 | ReentrantReadWriteLock |
原则:
- 简单场景用 synchronized(更简洁、更安全)
- 需要高级特性时用 Lock
- 记住在 finally 中释放锁
