在 java 并发编程中,synchronized 和 lock 是最常用的两种锁机制。它们都能保证线程安全,但实现原理、功能特性和性能表现上有显著差异。本文将带你彻底搞懂两者的区别,并通过流程图和对比表格助你做出正确的技术选型。
一、引言
线程安全是并发编程的核心问题。java 提供了多种锁机制,其中 synchronized 是 jvm 内置的关键字,而 lock 是 java.util.concurrent.locks 包下的接口(典型实现如 reentrantlock)。许多开发者只知道“lock 更灵活”,却不清楚具体灵活在哪里,以及什么场景该用哪个。
本文将围绕以下维度展开对比:
- 语法与使用方式
- 锁的获取与释放机制
- 可中断性、超时尝试
- 公平性选择
- 底层实现(偏向锁、轻量级锁、重量级锁 vs aqs)
- 性能表现(jdk 1.6 前后的变化)
最后给出实战建议与流程图,帮助你直观理解两者在锁竞争时的行为差异。
二、一句话总结核心区别(先入为主)
| 特性 | synchronized | lock(以 reentrantlock 为例) |
|---|---|---|
| 实现层级 | jvm 关键字,内置语言特性 | java 类库,基于 aqs 的 api |
| 锁的获取方式 | 隐式自动获取/释放 | 显式调用 lock() / unlock() |
| 可中断性 | 不可中断(除非抛出异常) | 支持 lockinterruptibly() |
| 超时获取锁 | 不支持 | 支持 trylock(timeout, unit) |
| 公平锁 | 非公平锁(仅) | 可公平(构造参数)也可非公平 |
| 条件变量 | 每个对象只有一个等待队列(wait/notify) | 一个 lock 可绑定多个 condition |
| 是否可重入 | 是 | 是(reentrantlock 可实现) |
| 锁释放方式 | 自动(方法结束或异常) | 必须手动在 finally 中释放 |
| 锁状态监控 | 无直接 api | 提供 trylock()、isheldbycurrentthread() 等 |
| 性能(低竞争) | 较好(jvm 优化,偏向锁) | 略差(对象创建开销) |
| 性能(高竞争) | 早期较差,jdk 1.6 后改善 | 稳定可控,吞吐量有时更高 |
三、深入对比:原理与代码示例
1. 语法与使用方式
synchronized:修饰实例方法、静态方法或代码块。
// 实例方法锁(当前实例)
public synchronized void method1() { /* 临界区 */ }
// 静态方法锁(class 对象)
public static synchronized void method2() { /* 临界区 */ }
// 代码块锁(自定义对象)
public void method3() {
synchronized (this) {
// 临界区
}
}
lock:需要显式创建锁对象,并在 finally 块中释放。
lock lock = new reentrantlock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须手动释放,否则死锁
}
常见错误:忘记在 finally 中 unlock(),导致锁无法释放。这也是 synchronized 的“自动释放”优势所在。
2. 锁的可中断性
synchronized线程在等待获取锁时不可被中断(thread.interrupt()无效,会一直阻塞)。lock提供了lockinterruptibly(),允许等待锁的线程响应中断。
lock.lockinterruptibly(); // 等待时可被中断
try {
// ...
} catch (interruptedexception e) {
// 处理中断
} finally {
lock.unlock();
}
3. 超时获取锁(避免死锁)
synchronized 无法尝试获取锁一段时间后放弃。lock 的 trylock(long time, timeunit unit) 能实现非阻塞尝试:
if (lock.trylock(1, timeunit.seconds)) {
try {
// 获取成功
} finally {
lock.unlock();
}
} else {
// 未获取到锁,执行其他逻辑
}
4. 公平性
synchronized只支持非公平锁(等待队列中随机或按操作系统调度,可能线程饿死)。reentrantlock可选公平锁:new reentrantlock(true)。公平锁保证等待时间最长的线程先获得锁,但会降低吞吐量。
lock fairlock = new reentrantlock(true); // 公平锁 lock unfairlock = new reentrantlock(false); // 非公平锁(默认)
5. 条件变量(condition)
synchronized 通过 wait()、notify() / notifyall() 实现等待/通知,每个对象只有一个等待集。lock 可以创建多个 condition 对象,实现更精细的线程唤醒。
reentrantlock lock = new reentrantlock();
condition notfull = lock.newcondition();
condition notempty = lock.newcondition();
// 生产者
lock.lock();
try {
while (队列满) notfull.await();
// 生产
notempty.signal();
} finally { lock.unlock(); }
四、底层实现原理简述
synchronized 的升级过程(jdk 1.6 优化)
在 jdk 1.6 之前,synchronized 是重量级锁(依赖操作系统的 mutex,用户态到内核态切换代价大)。1.6 之后引入了偏向锁 → 轻量级锁 → 重量级锁的升级过程:
- 偏向锁:锁总是被同一个线程获取,无需 cas。
- 轻量级锁:多线程交替执行,使用 cas 尝试获取锁,不阻塞。
- 重量级锁:竞争激烈,阻塞线程。

lock(reentrantlock)的 aqs 原理
reentrantlock 基于 aqs(abstractqueuedsynchronizer),内部维护一个 fifo 等待队列和一个 volatile int state 表示锁状态。通过 cas 设置状态,失败则加入等待队列并阻塞(locksupport.park())。
两者底层差异决定:
synchronized的锁释放由 jvm 保证(异常或方法结束),不会遗漏。lock的灵活性更高,但手动释放容易出错。
五、流程图对比:锁获取流程
synchronized 获取锁流程

lock(reentrantlock)获取锁流程

六、性能测试与选型建议
性能历史
- jdk 1.5:
synchronized性能很差,reentrantlock明显更优。 - jdk 1.6+:
synchronized经过优化(偏向锁、轻量级锁、适应性自旋),低竞争场景下性能与lock相当甚至略好。 - 高竞争场景:两者性能差别不大,但
lock提供了更多控制(如公平锁、可中断)。
选择指南
| 场景 | 推荐锁 | 原因 |
|---|---|---|
| 简单同步代码块,无需高级特性 | synchronized | 简洁、安全、自动释放 |
| 需要尝试获取锁并超时退出 | lock | trylock 超时机制 |
| 需要可中断的锁获取 | lock | lockinterruptibly |
| 需要多个条件变量(生产者-消费者) | lock | condition 更灵活 |
| 需要公平锁 | lock | synchronized 不支持 |
| 性能要求极高且竞争极低 | synchronized | 偏向锁开销小 |
| 锁粗粒度,手动控制释放时机 | lock | 可在不同方法间释放 |
七、实战代码对比:模拟售票系统
以下示例展示两种方式实现线程安全的余票扣除。
使用 synchronized
class ticketsync {
private int tickets = 100;
public synchronized boolean sell() {
if (tickets <= 0) return false;
tickets--;
return true;
}
}
使用 reentrantlock
class ticketlock {
private int tickets = 100;
private final lock lock = new reentrantlock();
public boolean sell() {
lock.lock();
try {
if (tickets <= 0) return false;
tickets--;
return true;
} finally {
lock.unlock();
}
}
}
八、常见误区
1.“lock 一定比 synchronized 快”
错。jdk 1.6 后,低竞争下 synchronized 有偏向锁优化,性能不差;高竞争下两者几乎持平。
2.“synchronized 不可重入”
错。synchronized 是可重入锁,同一个线程可以多次进入。
3.“lock 必须要 try-finally,否则死锁”
对。若临界区抛出异常未释放锁,其他线程将永久等待。synchronized 由 jvm 保证释放。
4.“公平锁一定比非公平锁好”
错。公平锁减少了线程饿死,但上下文切换更频繁,吞吐量通常更低。
九、总结与思维导图

最终建议:
- 优先使用
synchronized,除非遇到必须使用lock的场景(如超时、中断、多条件)。 - 永远不要自己实现锁,使用 juc 包下的
lock实现。 - 手动
lock时,unlock()必须放在finally块中。
到此这篇关于java中synchronized与lock锁机制的区别详解的文章就介绍到这了,更多相关java synchronized与lock区别内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论