Skip to main content

Java 锁机制详解

锁是Java并发编程中实现线程同步的核心机制,理解各种锁的特性和使用场景对于构建高性能的并发应用至关重要。本文将详细介绍Java中的各种锁机制。

核心价值

Java锁机制 = 线程同步 + 资源保护 + 执行顺序控制 + 内存可见性

  • 🔒 同步访问:确保共享资源的互斥访问
  • 🛡️ 数据保护:避免数据竞争和状态不一致
  • 高性能:通过不同锁策略优化并发性能
  • 🔄 执行控制:管理线程的执行顺序和时序关系
  • 👁️ 可见性:确保线程间数据修改的可见性

1. 锁机制概述

1.1 什么是锁?

核心概念

锁是一种同步机制,用于控制多个线程对共享资源的访问。锁确保在任意时刻只有一个线程能够访问被保护的资源,从而保证数据的一致性和完整性。

1.2 锁的分类

分类维度类型特点适用场景
实现方式synchronized内置锁,自动管理简单同步需求
Lock接口显式锁,手动管理复杂同步需求
公平性非公平锁性能高,不保证顺序一般场景
公平锁保证FIFO顺序需要公平性场景
可重入性可重入锁同一线程可多次获取递归调用场景
不可重入锁同一线程只能获取一次简单场景
读写特性独占锁只允许一个线程访问写操作
读写锁允许多个读,单个写读多写少场景
乐观/悲观悲观锁假设会发生冲突,先获取锁高并发写操作
乐观锁假设不会发生冲突,检测更新读多写少场景
锁分类详细图

1.3 锁的性能特性

锁类型性能功能使用复杂度死锁风险活跃性
synchronized中等基础功能简单中等不可中断
ReentrantLock丰富中等低(可超时)可中断
ReadWriteLock读多写少场景高分离读写中等中等可中断
StampedLock非常高乐观读复杂
乐观锁(CAS)低竞争下高无阻塞复杂

2. synchronized 关键字

2.1 synchronized 基本用法

java
1public class Counter {
2 private int count = 0;
3
4 // 实例方法同步 - 锁是当前实例(this)
5 public synchronized void increment() {
6 count++;
7 }
8
9 public synchronized int getCount() {
10 return count;
11 }
12}
13
14// 使用方式
15Counter counter = new Counter();
16// 多个线程调用同一实例的方法
17counter.increment(); // 线程安全
18
19// 注意: 不同实例之间的方法调用不受影响
20Counter counter1 = new Counter();
21Counter counter2 = new Counter();
22// 这两个调用可以并发执行,互不干扰
23counter1.increment();
24counter2.increment();

2.2 synchronized 的特性

可重入性(Reentrancy)是指同一个线程可以多次获取同一把锁。

java
1public class ReentrantExample {
2 // 可重入性示例
3 public synchronized void outer() {
4 System.out.println("Entering outer method");
5 // 同一线程可以再次获取相同的锁
6 inner();
7 System.out.println("Exiting outer method");
8 }
9
10 public synchronized void inner() {
11 System.out.println("Executing inner method");
12 }
13}
14
15// 执行结果:
16// Entering outer method
17// Executing inner method <-- 可以重入获取锁
18// Exiting outer method

2.3 synchronized 的局限性

synchronized 局限性概览

局限性描述解决方案
无法中断等待线程等待锁时无法响应中断使用 ReentrantLock.lockInterruptibly()
无法设置超时无法设置获取锁的超时时间使用 ReentrantLock.tryLock(time, unit)
无法实现公平性无法保证FIFO顺序使用 ReentrantLock(true) 创建公平锁
无法实现读写分离无法让多个读线程并行访问使用 ReadWriteLock 读写锁
无法知道锁状态无法查询锁是否被占用使用 Lock 接口的相关方法
java
1import java.util.concurrent.locks.Lock;
2import java.util.concurrent.locks.ReentrantLock;
3
4public class InterruptionExample {
5 private final Object syncLock = new Object();
6 private final Lock interruptibleLock = new ReentrantLock();
7
8 public void synchronizedMethod() {
9 // synchronized 不响应中断
10 synchronized (syncLock) {
11 try {
12 System.out.println("获取synchronized锁");
13 Thread.sleep(10000); // 持有锁10秒
14 } catch (InterruptedException e) {
15 System.out.println("即使线程被中断,synchronized也会继续执行到完成");
16 Thread.currentThread().interrupt();
17 }
18 }
19 }
20
21 public void interruptibleMethod() {
22 try {
23 // ReentrantLock 可以响应中断
24 interruptibleLock.lockInterruptibly();
25 try {
26 System.out.println("获取可中断锁");
27 Thread.sleep(10000); // 尝试持有锁10秒
28 } finally {
29 interruptibleLock.unlock();
30 }
31 } catch (InterruptedException e) {
32 System.out.println("锁等待被中断,可以执行其他操作");
33 }
34 }
35
36 public static void main(String[] args) throws InterruptedException {
37 InterruptionExample example = new InterruptionExample();
38
39 // 先占用锁
40 Thread blocker = new Thread(() -> {
41 example.synchronizedMethod();
42 });
43 blocker.start();
44 Thread.sleep(100); // 确保blocker获得锁
45
46 // 尝试获取锁并中断
47 Thread waiter = new Thread(() -> {
48 try {
49 System.out.println("等待锁...");
50 example.synchronizedMethod(); // 这里会被阻塞
51 } finally {
52 System.out.println("waiter线程结束");
53 }
54 });
55
56 waiter.start();
57 Thread.sleep(1000); // 让waiter等待一段时间
58
59 System.out.println("尝试中断waiter线程");
60 waiter.interrupt(); // 中断waiter线程
61
62 // 注意: waiter线程会一直等待,直到blocker释放锁
63 // synchronized不响应中断
64 }
65}

3. Lock 接口

3.1 Lock 接口基本用法

java
1import java.util.concurrent.locks.Lock;
2import java.util.concurrent.locks.ReentrantLock;
3
4public class LockBasicExample {
5 private final Lock lock = new ReentrantLock();
6 private int counter = 0;
7
8 public void increment() {
9 // 获取锁
10 lock.lock();
11 try {
12 // 临界区 - 受锁保护的代码
13 counter++;
14 } finally {
15 // 必须在finally块中释放锁
16 lock.unlock();
17 }
18 }
19
20 public int getCount() {
21 lock.lock();
22 try {
23 return counter;
24 } finally {
25 lock.unlock();
26 }
27 }
28
29 public static void main(String[] args) {
30 LockBasicExample example = new LockBasicExample();
31
32 // 创建多个线程并发增加计数器
33 for (int i = 0; i < 10; i++) {
34 new Thread(() -> {
35 for (int j = 0; j < 1000; j++) {
36 example.increment();
37 }
38 }).start();
39 }
40
41 // 等待一段时间后查看结果
42 try {
43 Thread.sleep(1000);
44 } catch (InterruptedException e) {
45 Thread.currentThread().interrupt();
46 }
47
48 System.out.println("计数器最终值: " + example.getCount());
49 }
50}

3.2 ReentrantLock 高级特性

java
1import java.util.concurrent.locks.Lock;
2import java.util.concurrent.locks.ReentrantLock;
3
4public class LockInfoExample {
5 private final ReentrantLock lock = new ReentrantLock();
6
7 public void methodWithInfo() {
8 lock.lock();
9 try {
10 System.out.println("当前线程: " + Thread.currentThread().getName());
11 System.out.println("持有锁的线程: " + lock.getOwner());
12 System.out.println("等待队列长度: " + lock.getQueueLength());
13 System.out.println("是否有线程等待: " + lock.hasQueuedThreads());
14 System.out.println("当前线程是否在等待队列中: " + lock.hasQueuedThread(Thread.currentThread()));
15 } finally {
16 lock.unlock();
17 }
18 }
19}

4. 读写锁 (ReadWriteLock)

4.1 读写锁基本用法

读写锁原理与特性

读写锁允许并发读取但互斥写入,适合读多写少场景:

  1. 读锁共享:多个线程可以同时持有读锁
  2. 写锁独占:写锁是排他的,一次只能有一个线程持有
  3. 读写互斥:持有写锁时,其他线程无法获取读锁;持有读锁时,其他线程无法获取写锁
  4. 写锁优先级:在一些实现中,写锁优先级高于读锁,防止写入线程"饥饿"

ReentrantReadWriteLock具有以下特性:

  • 可重入性
  • 可选择公平/非公平锁
  • 锁降级(写锁→读锁)支持
  • 锁升级(读锁→写锁)不支持
java
1import java.util.concurrent.locks.ReadWriteLock;
2import java.util.concurrent.locks.ReentrantReadWriteLock;
3import java.util.concurrent.locks.Lock;
4
5public class ReadWriteLockBasic {
6 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
7 private final Lock readLock = rwLock.readLock();
8 private final Lock writeLock = rwLock.writeLock();
9
10 private String data = "初始数据";
11
12 // 读操作 - 允许并发读取
13 public String read() {
14 readLock.lock(); // 获取读锁
15 try {
16 System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
17 // 模拟读取操作耗时
18 Thread.sleep(100);
19 return data;
20 } catch (InterruptedException e) {
21 Thread.currentThread().interrupt();
22 return null;
23 } finally {
24 readLock.unlock(); // 释放读锁
25 }
26 }
27
28 // 写操作 - 独占访问
29 public void write(String newData) {
30 writeLock.lock(); // 获取写锁
31 try {
32 System.out.println(Thread.currentThread().getName() + " 写入数据: " + newData);
33 // 模拟写入操作耗时
34 Thread.sleep(200);
35 data = newData;
36 } catch (InterruptedException e) {
37 Thread.currentThread().interrupt();
38 } finally {
39 writeLock.unlock(); // 释放写锁
40 }
41 }
42
43 public static void main(String[] args) {
44 final ReadWriteLockBasic example = new ReadWriteLockBasic();
45
46 // 创建多个读线程
47 for (int i = 0; i < 5; i++) {
48 new Thread(example::read, "Reader-" + i).start();
49 }
50
51 // 创建写线程
52 new Thread(() -> example.write("新数据"), "Writer-1").start();
53
54 // 再创建读线程
55 for (int i = 5; i < 10; i++) {
56 new Thread(example::read, "Reader-" + i).start();
57 }
58 }
59}

4.2 读写锁的性能特性

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3import java.util.concurrent.TimeUnit;
4import java.util.concurrent.atomic.AtomicInteger;
5import java.util.concurrent.locks.ReadWriteLock;
6import java.util.concurrent.locks.ReentrantReadWriteLock;
7import java.util.concurrent.locks.Lock;
8import java.util.concurrent.locks.ReentrantLock;
9
10/**
11 * 读写锁与独占锁性能对比测试
12 */
13public class ReadWriteLockPerformanceTest {
14 // 测试数据
15 private int sharedData = 0;
16
17 // 计数器
18 private final AtomicInteger readCount = new AtomicInteger(0);
19 private final AtomicInteger writeCount = new AtomicInteger(0);
20
21 // 读写锁
22 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
23 private final Lock readLock = readWriteLock.readLock();
24 private final Lock writeLock = readWriteLock.writeLock();
25
26 // 独占锁
27 private final Lock exclusiveLock = new ReentrantLock();
28
29 /**
30 * 使用读写锁读取数据
31 */
32 public int readWithReadWriteLock() {
33 readLock.lock();
34 try {
35 // 模拟读取操作
36 Thread.sleep(1);
37 readCount.incrementAndGet();
38 return sharedData;
39 } catch (InterruptedException e) {
40 Thread.currentThread().interrupt();
41 return 0;
42 } finally {
43 readLock.unlock();
44 }
45 }
46
47 /**
48 * 使用读写锁写入数据
49 */
50 public void writeWithReadWriteLock(int value) {
51 writeLock.lock();
52 try {
53 // 模拟写入操作
54 Thread.sleep(5);
55 sharedData = value;
56 writeCount.incrementAndGet();
57 } catch (InterruptedException e) {
58 Thread.currentThread().interrupt();
59 } finally {
60 writeLock.unlock();
61 }
62 }
63
64 /**
65 * 使用独占锁读取数据
66 */
67 public int readWithExclusiveLock() {
68 exclusiveLock.lock();
69 try {
70 // 模拟读取操作
71 Thread.sleep(1);
72 readCount.incrementAndGet();
73 return sharedData;
74 } catch (InterruptedException e) {
75 Thread.currentThread().interrupt();
76 return 0;
77 } finally {
78 exclusiveLock.unlock();
79 }
80 }
81
82 /**
83 * 使用独占锁写入数据
84 */
85 public void writeWithExclusiveLock(int value) {
86 exclusiveLock.lock();
87 try {
88 // 模拟写入操作
89 Thread.sleep(5);
90 sharedData = value;
91 writeCount.incrementAndGet();
92 } catch (InterruptedException e) {
93 Thread.currentThread().interrupt();
94 } finally {
95 exclusiveLock.unlock();
96 }
97 }
98
99 /**
100 * 性能测试
101 */
102 public void performanceTest(int threadCount, int duration, boolean useReadWriteLock) {
103 System.out.println("开始测试 " +
104 (useReadWriteLock ? "ReadWriteLock" : "ReentrantLock") +
105 " 线程数: " + threadCount);
106
107 // 重置计数器
108 readCount.set(0);
109 writeCount.set(0);
110
111 ExecutorService executor = Executors.newFixedThreadPool(threadCount);
112
113 // 开始测试
114 long startTime = System.currentTimeMillis();
115
116 // 提交测试任务
117 for (int i = 0; i < threadCount; i++) {
118 final int threadId = i;
119 executor.submit(() -> {
120 try {
121 while (System.currentTimeMillis() - startTime < duration) {
122 // 90%的概率进行读操作,10%的概率进行写操作
123 if (Math.random() < 0.9) {
124 // 读操作
125 if (useReadWriteLock) {
126 readWithReadWriteLock();
127 } else {
128 readWithExclusiveLock();
129 }
130 } else {
131 // 写操作
132 if (useReadWriteLock) {
133 writeWithReadWriteLock(threadId);
134 } else {
135 writeWithExclusiveLock(threadId);
136 }
137 }
138 }
139 } catch (Exception e) {
140 e.printStackTrace();
141 }
142 });
143 }
144
145 // 等待测试完成
146 executor.shutdown();
147 try {
148 executor.awaitTermination(duration + 1000, TimeUnit.MILLISECONDS);
149 } catch (InterruptedException e) {
150 Thread.currentThread().interrupt();
151 }
152
153 // 测试结果
154 long endTime = System.currentTimeMillis();
155 long totalTime = endTime - startTime;
156
157 System.out.println("测试完成,用时: " + totalTime + "ms");
158 System.out.println("读操作次数: " + readCount.get() + ", 读操作每秒: " +
159 (readCount.get() * 1000 / totalTime));
160 System.out.println("写操作次数: " + writeCount.get() + ", 写操作每秒: " +
161 (writeCount.get() * 1000 / totalTime));
162 System.out.println("总操作次数: " + (readCount.get() + writeCount.get()) +
163 ", 总操作每秒: " + ((readCount.get() + writeCount.get()) * 1000 / totalTime));
164 System.out.println("--------------------------------------");
165 }
166
167 /**
168 * 主测试方法
169 */
170 public static void main(String[] args) {
171 ReadWriteLockPerformanceTest test = new ReadWriteLockPerformanceTest();
172
173 // 测试参数
174 int[] threadCounts = {4, 8, 16, 32};
175 int testDuration = 5000; // 测试时间5秒
176
177 System.out.println("===== ReadWriteLock vs ReentrantLock 性能测试 =====");
178 System.out.println("读写比例: 90% 读, 10% 写");
179 System.out.println("测试时间: " + testDuration + "ms");
180 System.out.println("--------------------------------------");
181
182 // 进行测试
183 for (int threadCount : threadCounts) {
184 // 测试独占锁
185 test.performanceTest(threadCount, testDuration, false);
186
187 // 测试读写锁
188 test.performanceTest(threadCount, testDuration, true);
189
190 System.out.println();
191 }
192 }
193}

5. 锁的最佳实践

5.1 锁的选择策略

核心原则

选择合适的锁机制需要考虑以下因素:

  • 简单场景:优先使用synchronized
  • 复杂需求:使用Lock接口(中断、超时、公平性)
  • 读多写少:使用读写锁
  • 性能要求:考虑无锁数据结构

5.2 避免死锁

避免死锁示例
java
1public class DeadlockPrevention {
2
3 /**
4 * 1. 固定锁顺序
5 */
6 public static class FixedLockOrder {
7 private final Object lock1 = new Object();
8 private final Object lock2 = new Object();
9
10 public void method1() {
11 synchronized (lock1) {
12 synchronized (lock2) {
13 System.out.println("method1 执行");
14 }
15 }
16 }
17
18 public void method2() {
19 // 使用相同的锁顺序
20 synchronized (lock1) {
21 synchronized (lock2) {
22 System.out.println("method2 执行");
23 }
24 }
25 }
26 }
27
28 /**
29 * 2. 使用超时机制
30 */
31 public static class TimeoutPrevention {
32 private final ReentrantLock lock1 = new ReentrantLock();
33 private final ReentrantLock lock2 = new ReentrantLock();
34
35 public void method1() {
36 if (lock1.tryLock()) {
37 try {
38 if (lock2.tryLock(5, TimeUnit.SECONDS)) {
39 try {
40 System.out.println("method1 执行");
41 } finally {
42 lock2.unlock();
43 }
44 } else {
45 System.out.println("method1 获取锁2超时");
46 }
47 } catch (InterruptedException e) {
48 Thread.currentThread().interrupt();
49 } finally {
50 lock1.unlock();
51 }
52 }
53 }
54
55 public void method2() {
56 if (lock2.tryLock()) {
57 try {
58 if (lock1.tryLock(5, TimeUnit.SECONDS)) {
59 try {
60 System.out.println("method2 执行");
61 } finally {
62 lock1.unlock();
63 }
64 } else {
65 System.out.println("method2 获取锁1超时");
66 }
67 } catch (InterruptedException e) {
68 Thread.currentThread().interrupt();
69 } finally {
70 lock2.unlock();
71 }
72 }
73 }
74 }
75
76 /**
77 * 3. 使用锁的层次结构
78 */
79 public static class LockHierarchy {
80 private final Object lock1 = new Object();
81 private final Object lock2 = new Object();
82 private final Object lock3 = new Object();
83
84 public void method1() {
85 synchronized (lock1) {
86 synchronized (lock2) {
87 synchronized (lock3) {
88 System.out.println("method1 执行");
89 }
90 }
91 }
92 }
93
94 public void method2() {
95 synchronized (lock1) {
96 synchronized (lock2) {
97 System.out.println("method2 执行");
98 }
99 }
100 }
101
102 public void method3() {
103 synchronized (lock1) {
104 System.out.println("method3 执行");
105 }
106 }
107 }
108}

5.3 性能优化技巧

锁性能优化示例
java
1public class LockPerformanceOptimization {
2
3 /**
4 * 1. 减少锁的粒度
5 */
6 public static class FineGrainedLocking {
7 private final Object[] locks = new Object[16];
8 private final int[] counters = new int[16];
9
10 public FineGrainedLocking() {
11 for (int i = 0; i < locks.length; i++) {
12 locks[i] = new Object();
13 }
14 }
15
16 public void increment(int key) {
17 int index = Math.abs(key % locks.length);
18 synchronized (locks[index]) {
19 counters[index]++;
20 }
21 }
22
23 public int getTotal() {
24 int total = 0;
25 for (int i = 0; i < locks.length; i++) {
26 synchronized (locks[i]) {
27 total += counters[i];
28 }
29 }
30 return total;
31 }
32 }
33
34 /**
35 * 2. 使用读写锁提高并发性
36 */
37 public static class ReadWriteOptimization {
38 private final ReadWriteLock lock = new ReentrantReadWriteLock();
39 private final Lock readLock = lock.readLock();
40 private final Lock writeLock = lock.writeLock();
41 private final Map<String, String> data = new HashMap<>();
42
43 public String get(String key) {
44 readLock.lock();
45 try {
46 return data.get(key);
47 } finally {
48 readLock.unlock();
49 }
50 }
51
52 public void put(String key, String value) {
53 writeLock.lock();
54 try {
55 data.put(key, value);
56 } finally {
57 writeLock.unlock();
58 }
59 }
60
61 public void putAll(Map<String, String> map) {
62 writeLock.lock();
63 try {
64 data.putAll(map);
65 } finally {
66 writeLock.unlock();
67 }
68 }
69 }
70
71 /**
72 * 3. 使用条件变量避免忙等待
73 */
74 public static class ConditionOptimization {
75 private final ReentrantLock lock = new ReentrantLock();
76 private final Condition notEmpty = lock.newCondition();
77 private final Condition notFull = lock.newCondition();
78 private final Queue<String> queue = new LinkedList<>();
79 private final int capacity = 10;
80
81 public void put(String item) {
82 lock.lock();
83 try {
84 while (queue.size() >= capacity) {
85 notFull.await(); // 等待而不是忙等待
86 }
87 queue.offer(item);
88 notEmpty.signal();
89 } catch (InterruptedException e) {
90 Thread.currentThread().interrupt();
91 } finally {
92 lock.unlock();
93 }
94 }
95
96 public String take() {
97 lock.lock();
98 try {
99 while (queue.isEmpty()) {
100 notEmpty.await(); // 等待而不是忙等待
101 }
102 String item = queue.poll();
103 notFull.signal();
104 return item;
105 } catch (InterruptedException e) {
106 Thread.currentThread().interrupt();
107 return null;
108 } finally {
109 lock.unlock();
110 }
111 }
112 }
113}

6. 总结

Java锁机制是并发编程的核心,掌握各种锁的特性和使用场景对于构建高性能的并发应用至关重要。

6.1 关键要点

  1. 锁的类型:synchronized、Lock接口、读写锁
  2. 锁的特性:可重入性、公平性、中断性、超时性
  3. 避免死锁:固定锁顺序、超时机制、层次结构
  4. 性能优化:减少锁粒度、读写分离、条件变量

6.2 选择建议

场景推荐锁类型原因
简单同步synchronized简单易用,自动管理
复杂需求ReentrantLock功能丰富,支持中断和超时
读多写少ReadWriteLock提高并发性能
高性能要求无锁数据结构避免锁竞争

6.3 学习建议

  1. 理解原理:深入理解各种锁的工作原理
  2. 实践验证:通过编写代码验证不同锁的效果
  3. 性能测试:对比不同锁的性能差异
  4. 最佳实践:遵循锁使用的最佳实践

通过深入理解和熟练运用这些锁机制,我们能够构建出更加高效、健壮和可维护的Java并发应用程序。

参与讨论