J.U.C Review - 并发包下常见的锁接口和类

在这里插入图片描述


锁接口和类

前面我们介绍了Java的原生锁——基于对象的锁,通常与synchronized关键字一起使用。然而,Java在java.util.concurrent.locks包下还提供了更为强大和灵活的锁类和接口,这些锁可以提供更高的性能和更多的功能。

synchronized的不足之处

尽管synchronized是最早的同步机制之一,但它在处理多线程时存在一些不足:

  • 单线程执行限制:即使临界区是只读操作,synchronized也会限制同一时间只有一个线程执行,这可能导致不必要的性能瓶颈。
  • 锁状态不可查询synchronized无法查询当前线程是否成功获得了锁。
  • 线程阻塞问题:如果临界区因为IO操作或sleep等原因阻塞,而当前线程没有释放锁,其他线程会被迫等待,这可能会导致系统的整体吞吐量降低。

这些问题是java.util.concurrent.locks包中的锁类所能解决的。


锁的几种分类

锁的分类可以基于多种方式进行,下面详细介绍几种主要的分类方法:

可重入锁和非可重入锁

  • 可重入锁:允许同一线程对同一资源重复加锁,即使已经持有该锁的线程再次请求该锁也不会发生死锁。synchronized关键字实现的锁就是可重入的。例子:一个线程在一个synchronized方法中调用另一个本实例的synchronized方法时,能够继续获得锁而不产生死锁。

  • 非可重入锁:不允许同一线程对资源进行重复加锁。如果线程尝试重复加锁,可能会导致死锁或线程阻塞。

ReentrantLock是一个典型的可重入锁实现,允许线程对同一资源进行重复加锁。

公平锁与非公平锁

  • 公平锁:按照线程请求锁的顺序来分配锁,遵循FIFO原则。公平锁能够避免线程饥饿,但可能会带来较高的开销。

  • 非公平锁:不保证按照请求顺序分配锁,可能会导致某些线程长时间得不到锁,从而提高了锁的吞吐量,但可能会引起线程饥饿现象。

ReentrantLock支持两种模式:公平锁和非公平锁。公平锁的开销相对较高,但在一些对公平性要求较高的场景下是必需的。

读写锁和排它锁

  • 排它锁:在同一时刻只允许一个线程访问临界区。synchronizedReentrantLock都是排它锁。

  • 读写锁:允许多个线程同时读取,但在写入时会阻塞所有的读取和其他写入。ReentrantReadWriteLock是读写锁的实现,内部维护了一个读锁和一个写锁,以提升“读多写少”场景下的性能。

    注意,虽然读写锁在读取时允许多个线程并发,但在写操作时仍然会阻塞所有读线程和其他写线程。


JDK中有关锁的一些接口和类

Java JDK中提供了许多与锁相关的类和接口,主要集中在java.util.concurrent.locks包下。

以下是几个重要的类和接口:

抽象类AQS/AQLS/AOS

  • AQS (AbstractQueuedSynchronizer):提供了基本的队列同步器功能,内部使用一个int类型的资源表示。在JDK 1.6中引入了AQLS(AbstractQueuedLongSynchronizer)来支持更大范围的资源计数。AQSAQLS都继承自AOS(AbstractOwnableSynchronizer),后者用于表示锁与持有者之间的关系。

    AOS提供了设置和获取锁持有线程的方法。

    在这里插入图片描述

    // 独占模式,锁的持有者  
    private transient Thread exclusiveOwnerThread;  
    
    // 设置锁持有者  
    protected final void setExclusiveOwnerThread(Thread t) {  
        exclusiveOwnerThread = t;  
    }  
    
    // 获取锁的持有线程  
    protected final Thread getExclusiveOwnerThread() {  
        return exclusiveOwnerThread;  
    }
    

接口Condition/Lock/ReadWriteLock

  • Lock接口:提供了获取和释放锁的方法,以及获取Condition对象的方法。

在这里插入图片描述

  • Condition接口:提供类似于Objectwait/notify机制的方法,但功能更强大。Condition允许多个等待队列,并支持中断、超时等待等功能。
    在这里插入图片描述

每个对象都可以用继承自Objectwait/notify方法来实现等待/通知机制。而Condition接口也提供了类似Object监视器的方法,通过与Lock配合来实现等待/通知模式。

那为什么既然有Object的监视器方法了,还要用Condition呢?这里有一个二者简单的对比:

对比项Object监视器Condition
前置条件获取对象的锁调用Lock.lock获取锁,调用Lock.newCondition获取Condition对象
调用方式直接调用,比如object.notify()直接调用,比如condition.await()
等待队列的个数一个多个
当前线程释放锁进入等待状态支持支持
当前线程释放锁进入等待状态,在等待状态中不中断不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入等待状态直到将来的某个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

Condition和Object的wait/notify基本相似。其中,Condition的await方法对应的是Object的wait方法,而Condition的signal/signalAll方法则对应Object的notify/notifyAll()。但Condition类似于Object的等待/通知机制的加强版。我们来看看主要的方法:

方法名称描述
await()当前线程进入等待状态直到被通知(signal)或者中断;当前线程进入运行状态并从await()方法返回的场景包括:(1)其他线程调用相同Condition对象的signal/signalAll方法,并且当前线程被唤醒;(2)其他线程调用interrupt方法中断当前线程;
awaitUninterruptibly()当前线程进入等待状态直到被通知,在此过程中对中断信号不敏感,不支持中断当前线程
awaitNanos(long)当前线程进入等待状态,直到被通知、中断或者超时。如果返回值小于等于0,可以认定就是超时了
awaitUntil(Date)当前线程进入等待状态,直到被通知、中断或者超时。如果没到指定时间被通知,则返回true,否则返回false
signal()唤醒一个等待在Condition上的线程,被唤醒的线程在方法返回前必须获得与Condition对象关联的锁
signalAll()唤醒所有等待在Condition上的线程,能够从await()等方法返回的线程必须先获得与Condition对象关联的锁
  • ReadWriteLock接口:提供了获取读锁和写锁的方法。

    在这里插入图片描述

    public interface ReadWriteLock {
        Lock readLock();
        Lock writeLock();
    }
    

ReentrantLock

ReentrantLockLock接口的默认实现,支持公平锁和非公平锁。它通过内部的同步器实现了锁的基本功能,并且支持可重入特性。其内部结构包括两个同步器类:NonfairSyncFairSync,分别用于非公平锁和公平锁。

ReentrantLock 是 Java 中用于线程同步的一种可重入锁 (reentrant lock)。它是在 java.util.concurrent.locks 包中定义的,提供了比 synchronized 关键字更灵活和更强大的锁机制。ReentrantLock 是 Lock 接口的实现类,通过它可以显式地获取和释放锁,从而控制多个线程对共享资源的访问。

主要特点

  1. 可重入性:
    ReentrantLock 是一种可重入锁,即同一个线程可以多次获取同一把锁,而不会导致死锁。这意味着如果一个线程已经持有了该锁,并且再次请求获取该锁,ReentrantLock 会允许这个线程成功获取锁,并维护一个计数器,记录获取锁的次数。在释放锁时,必须与获取锁的次数匹配,这样锁才真正被释放。

  2. 公平锁与非公平锁:
    ReentrantLock 可以选择公平锁或非公平锁。默认情况下,ReentrantLock 是非公平的,这意味着线程可能不会按照请求锁的顺序获取锁,而是可能会发生“插队”现象。公平锁则严格按照请求的顺序来分配锁,从而避免线程饥饿问题。在创建 ReentrantLock 对象时,可以通过构造函数指定锁的公平性:

    ReentrantLock lock = new ReentrantLock(true); // 公平锁
    ReentrantLock lock = new ReentrantLock(false); // 非公平锁 (默认)
    
  3. 可中断的锁获取:
    使用 synchronized 关键字时,线程在等待锁时无法被中断。而 ReentrantLock 提供了可以响应中断的锁获取方式,允许线程在等待锁的过程中被中断:

    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        // 处理中断
    }
    
  4. 尝试获取锁:
    ReentrantLock 提供了 tryLock() 方法,它不会像 lock() 那样无限期等待锁,而是立即返回获取锁的结果。该方法可以设置超时时间,当锁不可用时,可以选择等待一段时间,如果在指定时间内无法获取锁,则返回 false

    if (lock.tryLock()) {
        // 成功获取锁
    } else {
        // 未能获取锁
    }
    
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        // 成功获取锁
    } else {
        // 在指定时间内未能获取锁
    }
    
  5. 条件变量(Condition):
    ReentrantLock 可以与 Condition 对象结合使用,提供比 Object 类的 wait()notify() 更强大的等待/通知机制。Condition 可以让线程在等待某个条件时释放锁,其他线程可以在条件满足时通知等待的线程继续执行:

    Condition condition = lock.newCondition();
    
    lock.lock();
    try {
        while (!conditionMet) {
            condition.await();  // 释放锁并进入等待状态
        }
        // 执行条件满足后的逻辑
    } finally {
        lock.unlock();
    }
    
    lock.lock();
    try {
        condition.signal();  // 唤醒等待中的线程
    } finally {
        lock.unlock();
    }
    

使用示例

基本使用

以下是一个使用 ReentrantLock 的简单示例,展示了如何在线程间共享资源时进行同步:

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++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        // 创建多个线程来同时访问共享资源
        for (int i = 0; i < 10; i++) {
            new Thread(example::increment).start();
        }

        // 主线程等待所有子线程执行完毕
        try {
            Thread.sleep(1000); // 简单的等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印最终计数结果
        System.out.println("Final count: " + example.getCount());
    }
}

在这个示例中,多个线程同时调用 increment() 方法增加 count 变量。为了保证线程安全,使用了 ReentrantLock 来同步对共享资源 count 的访问。最终,在所有线程执行完毕后,count 的值应该是线程数的总和。


Condition 使用 :生产者-消费者模式

下面是一个使用 ReentrantLockCondition 的代码示例。这个示例演示了如何使用 Condition 来实现线程间的协作,类似于生产者-消费者模式。生产者负责生成数据,消费者负责消费数据,Condition 用来管理生产者和消费者之间的同步。

package io.github.dunwu.javacore.concurrent.artisan;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 生产者消费者问题的条件变量示例类
 * 本类展示了如何使用条件变量来解决生产者消费者问题
 * 通过ReentrantLock和Condition来同步生产和消费操作
 */
public class ProducerConsumerConditionExample {

    // 定义一个重入锁,用于同步对队列的访问
    private final ReentrantLock lock = new ReentrantLock();

    // 当队列不为空时,notEmpty条件被触发,用于唤醒消费者
    private final Condition notEmpty = lock.newCondition();

    // 当队列不为满时,notFull条件被触发,用于唤醒生产者
    private final Condition notFull = lock.newCondition();

    // 使用LinkedList作为队列的基础实现
    private final Queue<Integer> queue = new LinkedList<>();

    // 定义队列的容量
    private final int capacity = 10;

    /**
     * 生产者方法,负责生成数据并放入队列中
     * 模拟生产者的行为,当队列满时等待,当队列不空时可以消费
     *
     * @throws InterruptedException 如果生产过程中被中断
     */
    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            lock.lock();
            try {
                // 当队列满时,生产者等待
                while (queue.size() == capacity) {
                    System.out.println("Queue is full, producer is waiting");
                    notFull.await(); // 通过调用await方法使生产者线程等待
                }

                // 生产数据并放入队列
                System.out.println("Produced " + value);
                queue.offer(value++);
                notEmpty.signal(); // 通知消费者队列不为空

            } finally {
                lock.unlock();
            }

            // 模拟生产延迟
            Thread.sleep(5000);
        }
    }


    /**
     * 消费者线程不断尝试从队列中消费元素的方法
     * 该方法通过使用条件变量实现生产者和消费者之间的同步
     *
     * @throws InterruptedException 如果线程被中断
     */
    public void consume() throws InterruptedException {
        while (true) {
            // 获取锁以确保线程安全
            lock.lock();
            try {
                // 如果队列为空,则消费者等待
                while (queue.isEmpty()) {
                    System.out.println("Queue is empty, consumer is waiting");
                    notEmpty.await(); // 队列空了,消费者等待
                }

                // 从队列头部取出一个元素并消费
                int value = queue.poll();
                System.out.println("Consumed " + value);
                // 通知生产者队列不满
                notFull.signal(); // 通知生产者队列不满

            } finally {
                // 释放锁
                lock.unlock();
            }

            // 模拟消费过程中的延迟
            Thread.sleep(1000); // 模拟消费的延迟
        }
    }

    /**
     * 程序的入口点 主要用于演示生产者-消费者问题的解决方案
     * 通过使用条件变量来同步生产和消费操作
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 创建生产者-消费者示例对象
        ProducerConsumerConditionExample example = new ProducerConsumerConditionExample();

        // 创建并启动生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                // 生产者方法,负责生产数据
                example.produce();
            } catch (InterruptedException e) {
                // 如果线程被中断,重新设置中断标志
                Thread.currentThread().interrupt();
            }
        });

        // 创建并启动消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                // 消费者方法,负责消费数据
                example.consume();
            } catch (InterruptedException e) {
                // 如果线程被中断,重新设置中断标志
                Thread.currentThread().interrupt();
            }
        });

        // 启动生产者线程
        producerThread.start();
        // 启动消费者线程
        consumerThread.start();
    }


}

代码说明

  1. 锁和条件变量

    • ReentrantLock lock:用于控制对共享资源(queue)的访问。
    • Condition notFull:生产者在队列满时等待的条件。
    • Condition notEmpty:消费者在队列为空时等待的条件。
  2. 生产者方法 (produce)

    • 当队列已满(queue.size() == capacity)时,生产者线程调用 notFull.await() 进入等待状态,直到被通知队列有空间可用。
    • 生产者将生成的数据添加到队列中后,调用 notEmpty.signal() 通知等待的消费者线程。
  3. 消费者方法 (consume)

    • 当队列为空时,消费者线程调用 notEmpty.await() 进入等待状态,直到被通知队列有数据可消费。
    • 消费者从队列中取出数据后,调用 notFull.signal() 通知等待的生产者线程。
  4. 线程启动

    • 创建并启动生产者线程和消费者线程,使它们在不同的线程中运行 produce()consume() 方法。

运行结果

  • 当队列为空时,消费者会等待,直到生产者添加了新数据。
  • 当队列已满时,生产者会等待,直到消费者消耗了数据。

这个例子展示了 ReentrantLockCondition 在线程间协作中的强大功能,特别是在需要更加细粒度控制的场景中,比起 synchronizedwait/notify 提供了更大的灵活性。


ReentrantReadWriteLock

ReentrantReadWriteLock实现了ReadWriteLock接口,支持读写锁。它内部维护了两个同步器,分别是ReadLockWriteLock。这两个锁共享同一个同步器实例,用于实现读写锁的功能。ReentrantReadWriteLock也支持公平锁和非公平锁。

与传统的互斥锁(如 ReentrantLock)不同,ReentrantReadWriteLock 允许多个线程同时读取数据(只要没有线程在写数据),但在写操作进行时,会阻塞所有的读操作和其他写操作。这种机制在读多写少的场景下非常有用,可以提高并发性能。

主要组件

ReentrantReadWriteLock 由两个锁组成:

  1. 读锁(ReadLock)

    • 允许多个线程同时获取,前提是没有线程持有写锁。
    • 如果有线程持有写锁,则读锁会被阻塞。
  2. 写锁(WriteLock)

    • 只允许一个线程获取,且当写锁被持有时,所有的读锁和写锁请求都会被阻塞。

主要方法

  • lock.readLock().lock()lock.readLock().unlock():获取和释放读锁。
  • lock.writeLock().lock()lock.writeLock().unlock():获取和释放写锁。

特性

  • 重入性ReentrantReadWriteLock 是可重入的,这意味着线程可以多次获得它已经持有的读锁或写锁,而不会被阻塞。
  • 锁降级:从写锁降级为读锁是允许的,前提是写锁在持有时获取读锁。锁的降级可以用来在写操作完成后安全地继续读取操作。

读写锁的使用

下面是一个使用 ReentrantReadWriteLock 的示例,演示了读写锁如何在读写操作中协调多个线程的访问。

package io.github.dunwu.javacore.concurrent.artisan;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁示例类
 */
public class ReadWriteLockExample {
    // 创建一个读写锁对象
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    // 获取读锁
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    // 获取写锁
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

    // 共享变量,初始值为0
    private int value = 0;

    /**
     * 写操作:更新值
     *
     * @param newValue 新的值
     */
    public void writeValue(int newValue) {
        // 获取写锁
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " writing " + newValue);
            // 更新值
            value = newValue;
            System.out.println(Thread.currentThread().getName() + " wrote " + newValue);
        } finally {
            // 释放写锁
            writeLock.unlock();
        }
    }

    /**
     * 读操作:读取值
     *
     * @return 当前值
     */
    public int readValue() {
        // 获取读锁
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " reading " + value);
            // 返回当前值
            return value;
        } finally {
            // 释放读锁
            readLock.unlock();
        }
    }

    /**
     * 主函数入口
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        // 启动多个线程进行读操作
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    example.readValue();
                }
            }, "Reader-" + i).start();
        }

        // 启动一个线程进行写操作
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.writeValue(i);
                try {
                    Thread.sleep(100); // 模拟写操作的延迟
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "Writer").start();
    }
}

代码说明

  1. 写锁使用

    • writeValue() 方法中,写操作通过 writeLock.lock() 加锁,确保在写操作期间没有其他线程能够进行读或写操作。
    • 写操作完成后,调用 writeLock.unlock() 释放写锁。
  2. 读锁使用

    • readValue() 方法中,读操作通过 readLock.lock() 加锁,确保多个线程能够同时读取,只要没有写操作进行。
    • 读操作完成后,调用 readLock.unlock() 释放读锁。
  3. 线程的并发执行

    • 启动多个读线程和一个写线程,读线程可以并发读取,而写线程在写入时会阻塞所有的读操作。

适用场景

ReentrantReadWriteLock 适合以下场景:

  • 读多写少:如果应用程序中读操作远多于写操作,使用 ReentrantReadWriteLock 可以提高并发性能。
  • 需要锁降级:在某些场景中,写操作完成后继续读取数据是有必要的,这时可以利用 ReentrantReadWriteLock 的锁降级特性。

StampedLock

StampedLock简介

StampedLock 是 Java 8 引入的一个新的锁机制,用于优化读写锁的性能。它设计的目的是为了减少读操作和写操作之间的竞争,提高并发性能。StampedLock 与传统的 ReadWriteLock 在功能和性能上有所不同。

设计背景和目的

传统的 ReadWriteLock 允许多个读线程并发读取,但写线程在写入时需要独占锁。虽然 ReadWriteLock 能够较好地处理读多写少的场景,但它的性能可能会受到读操作和写操作的锁争用的影响。StampedLock 引入了一个时间戳机制来管理锁的获取,从而在高并发的情况下提供更好的性能。

基本功能和使用方式

StampedLock 提供了三种主要的锁模式:

  1. 乐观读模式(Optimistic Read):在乐观读模式下,线程可以读取共享数据而不需要获取锁。乐观读模式不会阻塞写线程,因此性能通常优于传统的读锁。当读取完成后,线程需要验证读取期间是否有写操作发生,如果有,则需要重新读取数据。

  2. 悲观读模式(Pessimistic Read):线程在悲观读模式下获取一个读锁,确保在读操作期间没有其他线程写数据。与 ReadWriteLock 的读锁类似,悲观读锁也会阻塞写线程。

  3. 写模式(Write):线程在写模式下获取一个写锁,保证写操作期间没有其他线程读或写数据。写锁是独占的。

锁的获取和释放
  • 乐观读long stamp = stampedLock.tryOptimisticRead();
    stampedLock.validate(stamp);
    stampedLock.unlock(stamp); (如果需要)

  • 悲观读long stamp = stampedLock.readLock();
    stampedLock.unlockRead(stamp);

  • 写锁long stamp = stampedLock.writeLock();
    stampedLock.unlockWrite(stamp);

优缺点

优点:

  • 性能:在高并发的读操作场景中,乐观读模式能显著提高性能。
  • 灵活性:可以在乐观读和悲观读之间切换,提高了灵活性。

缺点:

  • 复杂性:使用乐观读模式时需要处理验证过程,增加了编程复杂度。
  • 死锁风险:如果使用不当,尤其是在锁的升级和降级时,可能会导致死锁。

示例代码

package io.github.dunwu.javacore.concurrent.artisan;
import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    // 声明一个StampedLock对象,用于实现乐观读、悲观读和写锁
    private final StampedLock stampedLock = new StampedLock();
    // 声明一个可被多个读取器共享的值
    private int value = 0;

    /**
     * 乐观读取方法
     * 首先尝试进行无锁读取,如果在读取期间检测到写操作,则转换为有锁读取
     * @return 当前的value值
     */
    public int optimisticRead() {
        // 尝试乐观读取,不需要锁
        long stamp = stampedLock.tryOptimisticRead();
        int currentValue = value;
        // 检查读取期间是否有写操作发生
        if (!stampedLock.validate(stamp)) {
            // 发现写操作,转换为有锁读取
            stamp = stampedLock.readLock();
            try {
                currentValue = value;
            } finally {
                stampedLock.unlockRead(stamp);
            }
        }
        return currentValue;
    }

    /**
     * 悲观读取方法
     * 在读取之前获取一个读锁,确保在读取期间不会有写操作发生
     * @return 当前的value值
     */
    public int pessimisticRead() {
        // 获取一个读锁
        long stamp = stampedLock.readLock();
        try {
            return value;
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }

    /**
     * 写操作方法
     * 在写操作之前获取一个写锁,确保在写操作期间不会有其他读或写操作发生
     * @param newValue 要写入的新值
     */
    public void write(int newValue) {
        // 获取一个写锁
        long stamp = stampedLock.writeLock();
        try {
            value = newValue;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
    }

    public static void main(String[] args) {
        StampedLockExample example = new StampedLockExample();

        // 测试写操作
        System.out.println("Writing value 10");
        example.write(10);

        // 测试乐观读取
        System.out.println("Optimistic Read (initial): " + example.optimisticRead());

        // 模拟另一个线程的写操作
        System.out.println("Writing value 20 (simulate concurrent modification)");
        example.write(20);

        // 测试乐观读取在修改后的结果
        int optimisticValue = example.optimisticRead();
        System.out.println("Optimistic Read (after write): " + optimisticValue);

        // 测试悲观读取
        System.out.println("Pessimistic Read: " + example.pessimisticRead());

        // 最后的写操作
        System.out.println("Final write value 30");
        example.write(30);
        System.out.println("Final value (pessimistic read): " + example.pessimisticRead());
    }
}

与其他锁机制的比较

  • ReadWriteLock 的比较

    • StampedLock 提供的乐观读模式比 ReadWriteLock 的读锁性能更好,尤其是在读操作远多于写操作的情况下。
    • StampedLock 的锁获取和释放操作较为复杂,而 ReadWriteLock 更直观易用。
  • ReentrantLock 的比较

    • StampedLock 的读写分离机制比 ReentrantLock 的互斥锁机制在读多写少的场景下能提供更高的并发性能。
    • StampedLock 允许悲观和乐观读锁,而 ReentrantLock 只提供互斥锁功能,不能直接优化读操作。

在这里插入图片描述

  • 22
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值