锁(补充中..)
本文介绍锁
ReentrantLock:可重入锁(JDK提供)
Java并发基石ReentrantLock:深入解读其原理与实现-腾讯云开发者社区-腾讯云
- 最常用、最典型的
Lock实现。- 支持可重入(同一个线程可多次加锁)。
- 提供公平锁和非公平锁两种模式(构造函数可选)。
- 基于 AQS(
AbstractQueuedSynchronizer) 实现。
它的“可重入”特性意味着:如果一个线程已经持有了该锁,那么它可以在不释放锁的情况下再次调用
lock()方法而不会被阻塞,也不会报错。
示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock(); // 第一次获取锁,hold count = 1
try {
System.out.println("第一次加锁");
lock.lock(); // 再次加锁,hold count = 2
try {
System.out.println("第二次加锁");
} finally {
lock.unlock(); // hold count = 1
}
} finally {
lock.unlock(); // hold count = 0,真正释放锁
}
}
}
ReentrantReadWriteLock:读写锁(JDK提供)
注意:ReentrantReadWriteLock 本身不直接实现 Lock 接口,但它的两个内部类 ReadLock 和 WriteLock 都实现了 Lock。
ReentrantReadWriteLock 适用于读远多于写的场景,能显著提升并发性能。合理使用读写锁,可避免 synchronized 的过度串行化问题。
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock的内部类。实现了Lock接口,代表读锁。 多个线程可同时持有读锁(只要没有写锁)。 读锁是可重入的。
ReentrantReadWriteLock.WriteLock
ReentrantReadWriteLock的另一个内部类。实现了Lock接口,代表写锁。 写锁是独占的(同一时间只能一个线程持有)。 写锁也是可重入的,并且支持锁降级(先获取写锁,再获取读锁,然后释放写锁)。
示例代码:
示例代码
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheExample {
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() + " 获取读锁,读取 key: " + key);
return cache.get(key);
} finally {
readLock.unlock();
}
}
// 写操作:仅一个线程可写
public void put(String key, String value) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取写锁,写入 key: " + key);
try {
Thread.sleep(100); // 模拟写入耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
// 演示锁降级:先写后读(在持有写锁的情况下获取读锁)
public String getOrCreate(String key, String defaultValue) {
writeLock.lock();
try {
String value = cache.get(key);
if (value == null) {
// 模拟计算或加载
value = defaultValue + "_computed";
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入新值: " + value);
}
// 锁降级:在释放写锁前先获取读锁
readLock.lock(); // 降级关键步骤
try {
writeLock.unlock(); // 释放写锁,但仍然持有读锁
System.out.println(Thread.currentThread().getName() + " 降级为读锁,返回: " + value);
return value;
} finally {
readLock.unlock(); // 最终释放读锁
}
} finally {
// 注意:写锁已在 try 块中释放,所以这里不能再次 unlock
// 因此上面的结构必须小心处理
}
}
// 简化版(无锁降级)更安全的写法:
public String getOrCreateSafe(String key, String defaultValue) {
// 先尝试读(无锁竞争时快速返回)
readLock.lock();
String value = cache.get(key);
if (value != null) {
try {
return value;
} finally {
readLock.unlock();
}
}
readLock.unlock(); // 注意:此处需确保未在 try 中,否则可能重复 unlock
// 未命中,升级为写锁
writeLock.lock();
try {
// 双重检查(防止多个线程同时写)
value = cache.get(key);
if (value == null) {
value = defaultValue + "_computed";
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入新值: " + value);
}
// 保持写锁直到结束(不降级),或直接返回
return value;
} finally {
writeLock.unlock();
}
}
// 测试主方法
public static void main(String[] args) throws InterruptedException {
CacheExample cache = new CacheExample();
// 启动多个读线程
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
String val = cache.get("name");
if (val == null) {
cache.put("name", "Alice");
}
}, "Reader-" + id).start();
}
// 启动一个写线程
new Thread(() -> {
cache.put("age", "30");
}, "Writer").start();
Thread.sleep(2000); // 等待线程执行完成
}
}
关键点说明:
- 读锁(ReadLock):
- 多个线程可同时持有读锁。
- 读锁期间不能有写锁。
- 写锁(WriteLock):
- 独占,同一时间只能一个线程持有。
- 写锁会阻塞所有读和其他写。
- 可重入性:
- 同一线程可以多次获取读锁或写锁(计数器机制)。
- 锁降级(Lock Downgrading):
- 允许从写锁降级为读锁(先获取写锁 → 再获取读锁 → 释 放写锁 → 最后释放读锁)。
- 不允许升级(即持有读锁时不能直接获取写锁,会导致死锁)。
- 双重检查(Double-Check):
- 在 getOrCreateSafe 中展示了一种更常见的“先读再写”模式,避免不必要的写锁开销。
⚠️ 注意事项:
-
不要在持有读锁时尝试获取写锁(会导致死锁,因为写锁需等待所有读锁释放,而当前线程自己持有一个读锁)。
-
锁降级代码要小心 unlock() 顺序,避免 IllegalMonitorStateException。
-
推荐优先使用 “先读再写” + 双重检查 的模式,比锁降级更简单安全。
概念
无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁:
在 Java 的并发机制中,synchronized 关键字的底层实现经历了从 JDK 1.6 开始的重大优化,引入了 锁升级(Lock Elimination & Lock Coarsening) 和 四种锁状态:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
这些锁状态是 JVM 为了在不同竞争程度下平衡性能与开销而设计的。下面逐一详解: