Java 并发编程深度解析:从 synchronized 到 Lock 的演进与实践

Java 并发编程深度解析:从 synchronized 到 Lock 的演进与实践

引言

Java 并发编程一直是后端开发中的核心技术领域,随着多核处理器的普及和高并发应用的需求增长,掌握并发编程的核心原理和最佳实践变得尤为重要。本文将深入解析 Java 并发编程中的关键技术点,从传统的 synchronized 关键字到现代的 Lock 接口,通过具体的代码实例和原理分析,帮助开发者理解并发编程的本质和演进过程。

synchronized 关键字:Java 并发的基石

基本原理与实现

synchronized 是 Java 提供的内置锁机制,基于对象监视器(Monitor)实现。每个 Java 对象都有一个内置的监视器锁,当线程进入 synchronized 代码块时,会自动获取该锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SynchronizedExample {
private int counter = 0;
private final Object lock = new Object();

// 方法级别的同步
public synchronized void incrementMethod() {
counter++;
}

// 代码块级别的同步
public void incrementBlock() {
synchronized (lock) {
counter++;
}
}

// 静态方法同步(类级别锁)
public static synchronized void staticMethod() {
// 使用 Class 对象作为锁
System.out.println("静态同步方法");
}
}

synchronized 的底层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 编译后的字节码会包含 monitorenter 和 monitorexit 指令
public void synchronizedMethod() {
synchronized (this) {
// 临界区代码
// monitorenter 指令
counter++;
// monitorexit 指令
}
}

// JVM 层面的实现原理
public class MonitorImplementation {
/**
* Monitor 的内部结构:
* 1. Owner: 当前持有锁的线程
* 2. EntryList: 等待获取锁的线程队列
* 3. WaitSet: 调用 wait() 方法的线程集合
* 4. Recursions: 重入次数计数器
*/

private volatile Thread owner; // 锁的持有者
private volatile int recursions; // 重入次数
private Queue<Thread> entryList; // 等待队列
private Set<Thread> waitSet; // 等待集合
}

synchronized 的优化演进

JDK 1.6 引入了锁优化技术,包括偏向锁、轻量级锁和重量级锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class LockOptimization {
private int value = 0;

public void demonstrateLockEscalation() {
// 1. 偏向锁阶段:只有一个线程访问
synchronized (this) {
value++; // 第一次访问,启用偏向锁
}

// 2. 轻量级锁阶段:少量线程竞争
new Thread(() -> {
synchronized (this) {
value++; // 出现竞争,升级为轻量级锁
}
}).start();

// 3. 重量级锁阶段:激烈竞争或长时间持有
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (this) {
try {
Thread.sleep(100); // 长时间持有,升级为重量级锁
value++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
}
}

Lock 接口:更灵活的并发控制

ReentrantLock 的核心特性

ReentrantLock 提供了比 synchronized 更丰富的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.TimeUnit;

public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int counter = 0;
private boolean ready = false;

// 1. 可中断的锁获取
public void interruptibleLock() throws InterruptedException {
lock.lockInterruptibly(); // 可以被中断的锁获取
try {
// 临界区代码
counter++;
} finally {
lock.unlock();
}
}

// 2. 尝试获取锁(非阻塞)
public boolean tryLockExample() {
if (lock.tryLock()) {
try {
counter++;
return true;
} finally {
lock.unlock();
}
}
return false; // 获取锁失败
}

// 3. 超时获取锁
public boolean timeoutLock() throws InterruptedException {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
counter++;
return true;
} finally {
lock.unlock();
}
}
return false; // 超时未获取到锁
}

// 4. 条件变量的使用
public void producer() throws InterruptedException {
lock.lock();
try {
// 生产数据
counter++;
ready = true;
condition.signalAll(); // 唤醒等待的消费者
} finally {
lock.unlock();
}
}

public void consumer() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 等待生产者通知
}
// 消费数据
System.out.println("消费数据: " + counter);
} finally {
lock.unlock();
}
}
}

AQS(AbstractQueuedSynchronizer)原理解析

ReentrantLock 基于 AQS 实现,AQS 是 Java 并发包的核心基础类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// AQS 的核心原理示意
public abstract class AbstractQueuedSynchronizer {
// 同步状态
private volatile int state;

// 等待队列的头节点
private transient volatile Node head;

// 等待队列的尾节点
private transient volatile Node tail;

// 节点结构
static final class Node {
volatile Thread thread; // 等待的线程
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile int waitStatus; // 等待状态
}

// 核心方法:尝试获取锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

// 核心方法:尝试释放锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
}

// ReentrantLock 中的 AQS 实现
public class ReentrantLock implements Lock {
private final Sync sync;

abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁的实现
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 状态为0,尝试CAS获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 重入逻辑
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
}

读写锁:优化读多写少场景

ReentrantReadWriteLock 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;

public class ReadWriteLockExample {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

// 读操作:多个线程可以同时读取
public String get(String key) {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在读取");
Thread.sleep(100); // 模拟读取耗时
return cache.get(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
readLock.unlock();
}
}

// 写操作:独占访问
public void put(String key, String value) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在写入");
Thread.sleep(200); // 模拟写入耗时
cache.put(key, value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
}
}

// 锁降级示例:写锁降级为读锁
public void lockDowngrade(String key, String value) {
writeLock.lock();
try {
// 写入数据
cache.put(key, value);

// 获取读锁(锁降级)
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁
}

try {
// 现在持有读锁,可以安全读取
String result = cache.get(key);
System.out.println("读取结果: " + result);
} finally {
readLock.unlock(); // 释放读锁
}
}
}

性能对比与选择指南

基准测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.CountDownLatch;

public class LockPerformanceTest {
private static final int THREAD_COUNT = 10;
private static final int ITERATIONS = 1000000;

private int synchronizedCounter = 0;
private int lockCounter = 0;
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();

// synchronized 性能测试
public void testSynchronized() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long startTime = System.currentTimeMillis();

for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < ITERATIONS; j++) {
synchronized (syncLock) {
synchronizedCounter++;
}
}
latch.countDown();
}).start();
}

latch.await();
long endTime = System.currentTimeMillis();
System.out.println("synchronized 耗时: " + (endTime - startTime) + "ms");
System.out.println("synchronized 结果: " + synchronizedCounter);
}

// ReentrantLock 性能测试
public void testReentrantLock() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long startTime = System.currentTimeMillis();

for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < ITERATIONS; j++) {
reentrantLock.lock();
try {
lockCounter++;
} finally {
reentrantLock.unlock();
}
}
latch.countDown();
}).start();
}

latch.await();
long endTime = System.currentTimeMillis();
System.out.println("ReentrantLock 耗时: " + (endTime - startTime) + "ms");
System.out.println("ReentrantLock 结果: " + lockCounter);
}
}

选择指南

场景 推荐方案 理由
简单的互斥访问 synchronized 语法简洁,JVM优化充分
需要超时或中断 ReentrantLock 提供更灵活的控制
读多写少场景 ReadWriteLock 读操作并发性能更好
公平性要求 ReentrantLock(fair) 支持公平锁模式
条件变量需求 ReentrantLock + Condition 比 wait/notify 更灵活

最佳实践与注意事项

1. 锁的正确使用模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LockBestPractices {
private final ReentrantLock lock = new ReentrantLock();

// ✅ 正确的锁使用模式
public void correctLockUsage() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock(); // 确保在 finally 中释放锁
}
}

// ❌ 错误的锁使用模式
public void incorrectLockUsage() {
lock.lock();
// 临界区代码
lock.unlock(); // 如果临界区抛异常,锁不会被释放
}

// ✅ 避免死锁的锁排序
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public void avoidDeadlock() {
// 始终按照相同的顺序获取锁
synchronized (lock1) {
synchronized (lock2) {
// 临界区代码
}
}
}
}

2. 性能优化建议

  • 减少锁的粒度:使用更细粒度的锁来减少竞争
  • 避免锁嵌套:减少死锁风险和性能开销
  • 合理选择锁类型:根据具体场景选择最适合的锁机制
  • 使用无锁数据结构:在可能的情况下使用 ConcurrentHashMap 等无锁容器

总结

Java 并发编程从 synchronized 到 Lock 的演进体现了对性能和灵活性的不断追求。synchronized 作为 Java 的内置锁机制,经过多年优化已经具备了很好的性能表现,适合大多数简单的同步场景。而 Lock 接口及其实现类则提供了更丰富的功能和更细粒度的控制,适合复杂的并发场景。

理解这些并发机制的底层原理和适用场景,能够帮助我们在实际开发中做出正确的技术选择,编写出高性能、线程安全的并发程序。随着 Java 并发编程的不断发展,掌握这些核心概念将为我们应对更复杂的并发挑战奠定坚实的基础。

在实际应用中,我们应该根据具体的业务场景和性能要求,选择最合适的并发控制机制,并始终遵循并发编程的最佳实践,确保程序的正确性和高效性。