哈尔滨企业建站模板产品推广平台有哪些
在 Java 并发编程中,线程同步是确保数据一致性和避免竞争条件的关键手段。synchronized
关键字和 java.util.concurrent.locks.Lock
接口是两种主流的同步工具,各有其设计理念和使用场景。作为 Java 开发者,理解两者的区别不仅有助于选择合适的工具,还能优化并发应用的性能和可维护性。本文将深入剖析 synchronized
和 Lock
的原理、功能差异及适用场景,并通过 Java 代码展示两者的实际应用。
一、Synchronized 与 Lock 的基本概念
1. Synchronized
synchronized
是 Java 的内置关键字,用于实现线程同步。它基于 JVM 的监视器(Monitor)机制,通过对象锁确保同一时刻只有一个线程访问受保护的代码块或方法。
- 使用方式:
- 同步代码块:锁定指定对象。
- 同步方法:锁定当前对象(
this
)或类对象(Class
)。
- 语法示例:
synchronized (obj) {// 同步代码块 } public synchronized void method() {// 同步方法 }
2. Lock
Lock
是 Java 5 引入的 java.util.concurrent.locks
包中的接口,提供了更灵活的锁机制。常见的实现类包括 ReentrantLock
,它通过显式加锁和解锁操作实现同步。
- 使用方式:
- 手动调用
lock()
加锁,unlock()
解锁。
- 手动调用
- 语法示例:
Lock lock = new ReentrantLock(); lock.lock(); try {// 同步代码 } finally {lock.unlock(); }
二、Synchronized 与 Lock 的核心区别
1. 实现机制
- Synchronized:
- 基于 JVM 内置的 Monitor 对象。
- 使用对象头中的 Mark Word 存储锁状态。
- 通过
monitorenter
和monitorexit
字节码指令实现。
- Lock:
- 基于 Java 代码和 AQS(
AbstractQueuedSynchronizer
)框架。 - 使用 volatile 变量和 CAS(Compare-And-Swap)管理锁状态。
- 基于 Java 代码和 AQS(
2. 使用方式
- Synchronized:
- 隐式加锁和解锁,JVM 自动管理。
- 无需显式释放锁,异常时自动解锁。
- Lock:
- 显式加锁和解锁,需手动调用
unlock()
。 - 必须在
finally
块中释放锁,否则可能导致死锁。
- 显式加锁和解锁,需手动调用
3. 功能特性
特性 | Synchronized | Lock |
---|---|---|
可重入性 | 支持 | 支持(ReentrantLock ) |
公平性 | 非公平 | 可选公平(ReentrantLock(true) ) |
中断性 | 不支持 | 支持(lockInterruptibly() ) |
超时尝试 | 不支持 | 支持(tryLock(timeout) ) |
条件变量 | 单一 wait/notify | 多个 Condition 对象 |
锁状态查询 | 不支持 | 支持(isLocked() ) |
4. 性能
- Synchronized:
- JDK 1.6 后优化,引入偏向锁、轻量级锁和重量级锁,性能显著提升。
- 在低竞争场景下开销小。
- Lock:
- 基于 AQS,竞争激烈时性能优于重量级锁。
- 高竞争下 CAS 操作可能导致自旋开销。
5. 异常处理
- Synchronized:异常后自动释放锁。
- Lock:异常后若未释放锁,可能导致死锁,需
finally
确保解锁。
三、底层原理剖析
1. Synchronized 的实现
synchronized
依赖 JVM 的 Monitor 机制:
- 对象头:Mark Word 记录锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
- 锁升级:
- 偏向锁:低竞争时偏向首个线程。
- 轻量级锁:少量竞争时使用 CAS。
- 重量级锁:高竞争时转为 Monitor。
- 字节码:
编译后:public void method() {synchronized (this) {System.out.println("Hello");} }
monitorenter getstatic java/lang/System.out ldc "Hello" invokevirtual java/io/PrintStream.println monitorexit
2. Lock 的实现
ReentrantLock
基于 AQS:
- 状态变量:
volatile int state
表示锁状态(0 为未锁,>0 为已锁)。 - 队列:AQS 维护等待线程的双向链表(CLH 变种)。
- CAS:通过
compareAndSetState
原子更新状态。 - 源码解析(
ReentrantLock.lock
):public void lock() {sync.acquire(1); } public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); } protected final boolean tryAcquire(int acquires) {Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}} else if (current == getExclusiveOwnerThread()) {setState(c + acquires);return true;}return false; }
四、功能对比与使用场景
1. 可重入性
两者都支持可重入:
- Synchronized:
public synchronized void outer() {inner(); } public synchronized void inner() {System.out.println("Inner called"); }
- Lock:
Lock lock = new ReentrantLock(); public void outer() {lock.lock();try {inner();} finally {lock.unlock();} } public void inner() {lock.lock();try {System.out.println("Inner called");} finally {lock.unlock();} }
2. 公平性
- Synchronized:非公平,线程随机获取锁。
- Lock:支持公平锁:
Lock fairLock = new ReentrantLock(true); // 公平锁
3. 中断性与超时
- Lock:
Lock lock = new ReentrantLock(); if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 操作} finally {lock.unlock();} } else {System.out.println("Lock not acquired"); }
4. 条件变量
- Synchronized:单一
wait/notify
:synchronized (obj) {while (condition) {obj.wait();}obj.notify(); }
- Lock:多个
Condition
:Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); lock.lock(); try {while (condition) {condition1.await();}condition2.signal(); } finally {lock.unlock(); }
五、Java 实践:对比 Synchronized 与 Lock
以下通过一个多线程计数器,展示两者的应用。
1. 环境准备
- 依赖(
pom.xml
):
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
2. Synchronized 实现
@RestController
@RequestMapping("/sync")
public class SyncCounterController {private int counter = 0;private final Object lock = new Object();@GetMapping("/increment")public String increment() {new Thread(() -> {for (int i = 0; i < 1000; i++) {synchronized (lock) {counter++;}}}).start();return "Synchronized increment started";}@GetMapping("/value")public int getValue() {synchronized (lock) {return counter;}}
}
3. Lock 实现
@RestController
@RequestMapping("/lock")
public class LockCounterController {private int counter = 0;private final Lock lock = new ReentrantLock();@GetMapping("/increment")public String increment() {new Thread(() -> {for (int i = 0; i < 1000; i++) {lock.lock();try {counter++;} finally {lock.unlock();}}}).start();return "Lock increment started";}@GetMapping("/value")public int getValue() {lock.lock();try {return counter;} finally {lock.unlock();}}@GetMapping("/try-increment")public String tryIncrement() throws InterruptedException {new Thread(() -> {for (int i = 0; i < 1000; i++) {if (lock.tryLock()) {try {counter++;} finally {lock.unlock();}}}}).start();return "Try-lock increment started";}
}
4. 主应用类
@SpringBootApplication
public class LockVsSyncApplication {public static void main(String[] args) {SpringApplication.run(LockVsSyncApplication.class, args);}
}
5. 测试
测试 1:基本同步
- Synchronized:
- 请求:
GET http://localhost:8080/sync/increment
(10 次) - 查询:
GET http://localhost:8080/sync/value
- 结果:
10000
- 请求:
- Lock:
- 请求:
GET http://localhost:8080/lock/increment
(10 次) - 查询:
GET http://localhost:8080/lock/value
- 结果:
10000
- 请求:
- 分析:两者均正确同步,计数器无竞争问题。
测试 2:超时与尝试锁
- Lock:
- 请求:
GET http://localhost:8080/lock/try-increment
(10 次) - 查询:
GET http://localhost:8080/lock/value
- 结果:可能小于
10000
(因tryLock
不阻塞)。
- 请求:
- Synchronized:无类似功能。
- 分析:
Lock
提供非阻塞选项,适合避免死锁。
测试 3:性能对比
- 代码:
public class PerformanceTest {private static final int THREADS = 100;private static final int INCREMENTS = 10000;public static void main(String[] args) throws InterruptedException {testSynchronized();testLock();}static void testSynchronized() throws InterruptedException {Object lock = new Object();int[] counter = {0};Thread[] threads = new Thread[THREADS];long start = System.currentTimeMillis();for (int i = 0; i < THREADS; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < INCREMENTS; j++) {synchronized (lock) {counter[0]++;}}});threads[i].start();}for (Thread t : threads) t.join();long end = System.currentTimeMillis();System.out.println("Synchronized: " + (end - start) + "ms, Counter: " + counter[0]);}static void testLock() throws InterruptedException {Lock lock = new ReentrantLock();int[] counter = {0};Thread[] threads = new Thread[THREADS];long start = System.currentTimeMillis();for (int i = 0; i < THREADS; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < INCREMENTS; j++) {lock.lock();try {counter[0]++;} finally {lock.unlock();}}});threads[i].start();}for (Thread t : threads) t.join();long end = System.currentTimeMillis();System.out.println("Lock: " + (end - start) + "ms, Counter: " + counter[0]);} }
- 结果(8 核 CPU):
Synchronized: 152ms, Counter: 1000000 Lock: 168ms, Counter: 1000000
- 分析:低竞争下
synchronized
略快,因 JVM 优化;高竞争下Lock
可通过公平性调整性能。
六、使用场景与选择建议
1. Synchronized 适用场景
- 简单同步需求:代码量少,无需复杂功能。
- 低竞争环境:偏向锁和轻量级锁高效。
- 异常安全要求高:无需手动释放锁。
2. Lock 适用场景
- 复杂同步逻辑:需要条件变量、中断或超时。
- 高竞争环境:公平锁或非阻塞尝试。
- 动态控制:需查询锁状态或自定义行为。
3. 选择建议
- 默认使用 Synchronized:简单场景,代码更简洁。
- 优先考虑 Lock:需要高级功能或性能优化。
七、优化与实践经验
1. 性能优化
- 减小锁范围:
synchronized (lock) {int temp = counter; // 读操作无需锁counter = temp + 1; // 仅写操作加锁 }
- 使用读写锁(
Lock
):ReadWriteLock rwLock = new ReentrantReadWriteLock(); rwLock.readLock().lock();
2. 异常处理
- Lock:
lock.lock(); try {// 操作 } catch (Exception e) {log.error("Error occurred", e); } finally {lock.unlock(); }
3. 调试技巧
- jstack:检查锁状态和死锁。
- VisualVM:监控线程和锁竞争。
八、总结
synchronized
和 Lock
是 Java 并发控制的两种利器,前者简单高效,依赖 JVM 内置机制;后者功能丰富,基于 AQS 提供灵活性。本文从实现原理、功能对比到实践应用,全面展示了它们的区别。测试表明,synchronized
适合简单场景,而 Lock
在复杂需求中更具优势。