StampedLock详解

在现代的Java应用中,同步是一个核心问题,尤其是在高并发环境下。Java提供了多种同步机制,从基本的synchronized关键字到更高级的ReentrantLock。但在Java 8中,引入了一个新的同步原语——StampedLock,它旨在提供更高的性能,特别是在读操作远多于写操作的场景中。

1.什么是StampedLock?

StampedLock是一个同步工具,它支持三种访问模式:写锁、乐观读和悲观读。这三种模式使得StampedLock能够在不同的使用场景下提供更高的吞吐量。
StampedLock 是 Java 8 引入的一种新的同步原语,用于替代 ReentrantLock 以提供更高的并发性能。它使用了一种称为 “乐观读”(optimistic reading)的技术,以及 “写锁”(write lock)和 “读锁”(read lock)的分离,以优化读多写少的场景。

2.特点

  1. 三种访问模式
  • 写锁:独占锁,用于修改数据。
  • 乐观读:不阻塞其他读或写,但在数据实际被读取前,会检查锁是否已被其他线程获取。
  • 悲观读:阻塞写但不阻塞其他读。
  1. 优化读操作:在大量读操作和较少写操作的场景中,StampedLock 可以提供更好的性能。
  2. 不可重入:与 ReentrantLock 不同,StampedLock 不是可重入的。
  3. 无条件公平性StampedLock 不提供任何公平性保证。

3.构造函数和相关方法

  1. 实例化
StampedLock lock = new StampedLock();
  1. 写锁
long stamp = lock.writeLock();
try {
   
    // 修改共享数据的代码
} finally {
   
    lock.unlockWrite(stamp);
}
  1. 乐观读
long stamp = lock.tryOptimisticRead();
// 读取共享数据的代码
if (!lock.validate(stamp)) {
   
    // 如果在读取过程中锁被其他线程获取,则执行以下代码
    stamp = lock.readLock();
    try {
   
        // 重新读取共享数据的代码
    } finally {
   
        lock.unlockRead(stamp);
    }
}
  1. 悲观读
long stamp = lock.readLock();
try {
   
    // 读取共享数据的代码
} finally {
   
    lock.unlockRead(stamp);
}

注意事项

  • 由于 StampedLock 不可重入,因此在同一个线程中多次获取同一个锁时,必须小心。
  • StampedLock 没有与 Condition 类似的机制,因此不适合需要等待/通知模式的场景。
  • 在使用乐观读时,需要注意 validate() 方法的调用,以确保在读取过程中锁没有被其他线程获取。

4.为什么选择StampedLock?

与传统的ReentrantLock相比,StampedLock在以下方面提供了优势:

  • 性能StampedLock通过乐观读和悲观读的分离,优化了读多写少的场景。在大量读操作的场景下,StampedLock可以提供比ReentrantLock更高的吞吐量。
  • 灵活性:开发者可以根据具体的使用场景选择合适的锁模式。例如,在数据更新不频繁,但读取非常频繁的场景下,乐观读可能是一个更好的选择。

5.使用示例

首先是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(乐观锁)则尝试在不阻塞的情况下读取计数器,但如果在读取过程中发生了写操作,则会重新读取。

由于乐观读不保证每次都能成功,所以在高并发环境下,乐观读计算的和可能会小于实际写入的次数。然而,在读多写少且写冲突不频繁的场景下,乐观读通常能够提供更高的吞吐量。

6.总结

StampedLock是一个强大的同步工具,它在特定的使用场景下可以提供比传统锁更高的性能。

相关推荐

  1. StampedLock详解

    2024-01-12 18:50:03       29 阅读
  2. 多线程(31)StampedLock和ReadWriteLock

    2024-01-12 18:50:03       15 阅读
  3. ReadWriteLock 和 StampedLock 的比较与解析

    2024-01-12 18:50:03       38 阅读
  4. 并发编程:线程同步基础:7、StampedLock DEMO

    2024-01-12 18:50:03       37 阅读
  5. Hive-DDL详解(超详细

    2024-01-12 18:50:03       33 阅读
  6. Hive-DML详解(超详细

    2024-01-12 18:50:03       29 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-12 18:50:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-12 18:50:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-12 18:50:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-12 18:50:03       18 阅读

热门阅读

  1. 服务器配置 ssh 密钥登录

    2024-01-12 18:50:03       35 阅读
  2. elementui表格中实现点击单个单元格触发事件

    2024-01-12 18:50:03       33 阅读
  3. 《微信小程序开发从入门到实战》学习七十九

    2024-01-12 18:50:03       33 阅读
  4. typescript递归数据结构的定义和处理

    2024-01-12 18:50:03       41 阅读
  5. Could not erase files or folders:

    2024-01-12 18:50:03       34 阅读
  6. umi + monorepo实践

    2024-01-12 18:50:03       30 阅读