🔒 高级锁机制
深入理解ReentrantLock、ReadWriteLock、StampedLock等高级锁技术,掌握公平锁与非公平锁的区别
学习目标
- 掌握ReentrantLock的使用和优势
- 理解公平锁和非公平锁的区别
- 学会使用ReadWriteLock优化读写场景
- 掌握Condition接口的使用
- 了解StampedLock的高级特性
ReentrantLock详解
ReentrantLock是Java并发包中提供的可重入锁,它提供了比synchronized更灵活的锁机制。与synchronized相比,ReentrantLock提供了更多的功能,如可中断锁、超时锁、公平锁等。
ReentrantLock支持可重入性,同一个线程可以多次获取同一把锁,每次获取锁时计数器加1,释放锁时计数器减1,当计数器为0时锁才真正被释放。
基本使用方法
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
System.out.println("Count: " + count);
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
System.out.println("Count: " + count);
} finally {
lock.unlock();
}
}
}
与synchronized的对比
- JVM内置锁机制
- 自动获取和释放锁
- 不可中断
- 非公平锁
- 无法设置超时
- API层面的锁
- 手动获取和释放锁
- 可中断锁
- 支持公平锁和非公平锁
- 支持超时机制
高级特性示例
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class AdvancedReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
// 可中断锁
public void interruptibleLock() throws InterruptedException {
lock.lockInterruptibly();
try {
// 执行业务逻辑
Thread.sleep(1000);
} finally {
lock.unlock();
}
}
// 超时锁
public boolean tryLockWithTimeout() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 执行业务逻辑
return true;
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return false;
}
}
公平锁vs非公平锁
公平锁和非公平锁是ReentrantLock的两种工作模式,它们在性能和公平性之间提供了不同的权衡。
公平锁特性
公平锁保证线程按照请求锁的顺序获得锁,先到先得,避免了线程饥饿问题。
- 按照FIFO顺序获取锁
- 避免线程饥饿
- 性能相对较低
- 适合对公平性要求高的场景
- 允许插队获取锁
- 可能导致线程饥饿
- 性能相对较高
- 适合对性能要求高的场景
性能对比示例
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.CountDownLatch;
public class FairVsUnfairLockTest {
private static final int THREAD_COUNT = 10;
private static final int ITERATIONS = 100000;
public static void testLock(ReentrantLock lock, String lockType) {
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++) {
lock.lock();
try {
// 模拟业务操作
} finally {
lock.unlock();
}
}
latch.countDown();
}).start();
}
try {
latch.await();
long endTime = System.currentTimeMillis();
System.out.println(lockType + " 耗时: " + (endTime - startTime) + "ms");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
// 测试公平锁
ReentrantLock fairLock = new ReentrantLock(true);
testLock(fairLock, "公平锁");
// 测试非公平锁
ReentrantLock unfairLock = new ReentrantLock(false);
testLock(unfairLock, "非公平锁");
}
}
ReadWriteLock读写锁
ReadWriteLock是一种特殊的锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入。这种设计在读多写少的场景下能显著提升性能。
读锁是共享锁,多个线程可以同时持有;写锁是排他锁,只能被一个线程持有,且与读锁互斥。
基本使用示例
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;
public class ReadWriteLockCache {
private final Map cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public V get(K key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(K key, V value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
public void remove(K key) {
lock.writeLock().lock();
try {
cache.remove(key);
} finally {
lock.writeLock().unlock();
}
}
public int size() {
lock.readLock().lock();
try {
return cache.size();
} finally {
lock.readLock().unlock();
}
}
}
锁降级示例
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockDowngradeExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private volatile boolean dataReady = false;
private String data;
public String getData() {
lock.readLock().lock();
try {
if (!dataReady) {
// 释放读锁,获取写锁
lock.readLock().unlock();
lock.writeLock().lock();
try {
// 双重检查
if (!dataReady) {
data = loadData(); // 加载数据
dataReady = true;
}
// 锁降级:在释放写锁之前获取读锁
lock.readLock().lock();
} finally {
lock.writeLock().unlock();
}
}
return data;
} finally {
lock.readLock().unlock();
}
}
private String loadData() {
// 模拟数据加载
return "Loaded Data";
}
}
Condition接口
Condition接口提供了类似Object.wait()和notify()的功能,但更加灵活。一个Lock可以关联多个Condition,实现更精确的线程通信。
生产者消费者模式
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;
public class BlockingQueue {
private final Queue queue = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 队列满时等待
}
queue.offer(item);
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 队列空时等待
}
T item = queue.poll();
notFull.signal(); // 通知生产者
return item;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
}
StampedLock高级特性
StampedLock是Java 8引入的新锁机制,它提供了乐观读锁功能,在读多写少的场景下性能更优。StampedLock不支持重入和Condition。
乐观读锁不会阻塞写操作,读取时先获取一个stamp,读取完成后验证stamp是否有效,如果无效则升级为悲观读锁。
基本使用示例
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
private double x, y;
// 写操作
public void write(double newX, double newY) {
long stamp = lock.writeLock();
try {
x = newX;
y = newY;
} finally {
lock.unlockWrite(stamp);
}
}
// 乐观读
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double curX = x, curY = y;
if (!lock.validate(stamp)) {
// 乐观读失败,升级为悲观读
stamp = lock.readLock();
try {
curX = x;
curY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
// 悲观读
public double[] getCoordinates() {
long stamp = lock.readLock();
try {
return new double[]{x, y};
} finally {
lock.unlockRead(stamp);
}
}
}
性能对比
- 乐观读不阻塞写操作
- 读多写少场景性能优异
- 支持锁升级和降级
- 内存占用更小
- 不支持重入
- 不支持Condition
- 使用复杂度较高
- 适合特定场景使用
实战练习
练习1:银行转账系统
使用ReentrantLock重新实现银行转账功能,避免死锁问题。
练习2:线程安全缓存
使用ReadWriteLock实现一个高性能的线程安全缓存系统。
练习3:生产者消费者
使用Condition接口实现一个支持多生产者多消费者的阻塞队列。
练习4:性能测试
对比synchronized、ReentrantLock、ReadWriteLock和StampedLock在不同场景下的性能表现。