在现代的Java应用中,同步是一个核心问题,尤其是在高并发环境下。Java提供了多种同步机制,从基本的synchronized
关键字到更高级的ReentrantLock
。但在Java 8中,引入了一个新的同步原语——StampedLock
,它旨在提供更高的性能,特别是在读操作远多于写操作的场景中。
StampedLock
是一个同步工具,它支持三种访问模式:写锁、乐观读和悲观读。这三种模式使得StampedLock
能够在不同的使用场景下提供更高的吞吐量。
StampedLock
是 Java 8 引入的一种新的同步原语,用于替代 ReentrantLock
以提供更高的并发性能。它使用了一种称为 “乐观读”(optimistic reading)的技术,以及 “写锁”(write lock)和 “读锁”(read lock)的分离,以优化读多写少的场景。
StampedLock
可以提供更好的性能。ReentrantLock
不同,StampedLock
不是可重入的。StampedLock
不提供任何公平性保证。StampedLock lock = new StampedLock();
long stamp = lock.writeLock();
try {
// 修改共享数据的代码
} finally {
lock.unlockWrite(stamp);
}
long stamp = lock.tryOptimisticRead();
// 读取共享数据的代码
if (!lock.validate(stamp)) {
// 如果在读取过程中锁被其他线程获取,则执行以下代码
stamp = lock.readLock();
try {
// 重新读取共享数据的代码
} finally {
lock.unlockRead(stamp);
}
}
long stamp = lock.readLock();
try {
// 读取共享数据的代码
} finally {
lock.unlockRead(stamp);
}
注意事项:
StampedLock
不可重入,因此在同一个线程中多次获取同一个锁时,必须小心。StampedLock
没有与 Condition
类似的机制,因此不适合需要等待/通知模式的场景。validate()
方法的调用,以确保在读取过程中锁没有被其他线程获取。与传统的ReentrantLock
相比,StampedLock
在以下方面提供了优势:
StampedLock
通过乐观读和悲观读的分离,优化了读多写少的场景。在大量读操作的场景下,StampedLock
可以提供比ReentrantLock
更高的吞吐量。首先是Counter
类,它使用StampedLock
来保护其内部计数器:
import java.util.concurrent.locks.StampedLock;
public class Counter {
private int count;
private final StampedLock lock = new StampedLock();
public void increment() {
long stamp = lock.writeLock();
try {
count++;
} finally {
lock.unlockWrite(stamp);
}
}
public int read() {
long stamp = lock.readLock();
try {
return count;
} finally {
lock.unlockRead(stamp);
}
}
public int optimisticRead() {
long stamp = lock.tryOptimisticRead();
int currentCount = count;
// 检查在读取过程中是否有写操作
if (!lock.validate(stamp)) {
// 如果写锁已被获取,则升级为悲观读锁
stamp = lock.readLock();
try {
currentCount = count;
} finally {
lock.unlockRead(stamp);
}
}
return currentCount;
}
}
接下来是测试类CounterTest
,它将创建多个线程来模拟并发读写操作:
public class CounterTest {
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
// 创建并启动写线程
Thread writer = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
// 创建并启动读线程
Thread reader = new Thread(() -> {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += counter.read();
}
System.out.println("Sum read via pessimistic lock: " + sum);
});
// 创建并启动乐观读线程
Thread optimisticReader = new Thread(() -> {
int optimisticSum = 0;
for (int i = 0; i < 1000; i++) {
optimisticSum += counter.optimisticRead();
}
System.out.println("Sum read via optimistic lock: " + optimisticSum);
});
// 启动所有线程
writer.start();
reader.start();
optimisticReader.start();
// 等待所有线程完成
writer.join();
reader.join();
optimisticReader.join();
// 打印最终计数器的值
System.out.println("Final counter value: " + counter.read());
}
}
运行结果:
Sum read via pessimistic lock: 999000
Sum read via optimistic lock: 990000
Final counter value: 1000
在这个例子中,pessimistic lock
(悲观锁)指的是使用readLock
方法获取的读锁,它保证在读取计数器时不会被写线程中断。而optimistic lock
(乐观锁)则尝试在不阻塞的情况下读取计数器,但如果在读取过程中发生了写操作,则会重新读取。
由于乐观读不保证每次都能成功,所以在高并发环境下,乐观读计算的和可能会小于实际写入的次数。然而,在读多写少且写冲突不频繁的场景下,乐观读通常能够提供更高的吞吐量。
StampedLock
是一个强大的同步工具,它在特定的使用场景下可以提供比传统锁更高的性能。