Java SDK 并发包全面总结(一)

简介: Java 并发包中的 Lock 和 Condition 主要解决的是线程的互斥和同步问题,这两者的配合使用,相当于 synchronized、wait()、notify() 的使用。

一、Lock 和 Condition

Java 并发包中的 Lock 和 Condition 主要解决的是线程的互斥和同步问题,这两者的配合使用,相当于 synchronized、wait()、notify() 的使用。

1. Lock 的优势

比起传统的 synchronized 关键字,Lock 最大的不同(或者说优势)在于:

  • 阻塞的线程能够响应中断,这样能够有机会释放自己持有的锁,避免死锁
  • 支持超时,如果线程在一定时间内未获取到锁,不是进入阻塞状态,而是抛出异常
  • 非阻塞的获取锁,如果未获取到锁,不进入阻塞状态,而是直接返回

三种情况分别对应 Lock 的三个方法:void lockInterruptibly()boolean tryLock(long time, TimeUnit unit)boolean tryLock()

Lock 最常用的一个实现类是 ReentrantLock,代表可重入锁,意思是可以反复获取同一把锁。

除此之外,Lock 的构造方法可以传入一个 boolean 值,表示是否是公平锁。

2. Lock 和 Condition 的使用

前面实现的简单的阻塞队列就是使用 Lock 和 Condition ,现在其含义已经非常明确了:

public class BlockingQueue<T> {
    private int capacity;
    private int size;
    //定义锁和条件
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    /**
     * 入队列
     */
    public void enqueue(T data){
        lock.lock();
        try {
            //如果队列满了,需要等待,直到队列不满
            while (size >= capacity){
                notFull.await();
            }
            //入队代码,省略
            //入队之后,通知队列已经不为空了
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //在finally块中释放锁,避免死锁
            lock.unlock();
        }
    }
    /**
     * 出队列
     */
    public T dequeue(){
        lock.lock();
        try {
            //如果队列为空,需要等待,直到队列不为空
            while (size <= 0){
                notEmpty.await();
            }
            //出队代码,省略
            //出队列之后,通知队列已经不满了
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        //实际应该返回出队数据
        return null;
    }
}

可以看到,Lock 需要手动的加锁和解锁,并且解锁操作是放在 finally 块中的,这是一种编程范式,尽量遵守。


二、ReadWriteLock

ReadWriteLock 表示读写锁,适用于读多写少的情况,读写锁一般有几个特征:

  • 读锁与读锁之间不互斥,即允许多个线程同时读变量。
  • 写锁与读锁之间互斥,一个线程在写时,不允许读操作。
  • 写锁与写锁之间互斥,只允许 一个线程写操作。

读写锁减小了锁的粒度,在读多写少的场景下,对性能的提升较为明显。ReadWriteLock 的简单使用示例如下:

public class ReadWriteLockTest {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock =lock.readLock();
    private final Lock writeLock =lock.writeLock();
    private int value;
    //加写锁
    private void addValue(){
        writeLock.lock();
        try {
            value += 1;
        }
        finally {
            writeLock.unlock();
        }
    }
    //加读锁
    private int getValue(){
        readLock.lock();
        try {
            return value;
        }
        finally {
            readLock.unlock();
        }
    }
}

读写锁的升级与降级

Java 中不允许锁的升级,即加写锁时必须释放读锁。

但是允许锁的降级,即加读锁时,可以不释放写锁,最后读锁和写锁一起释放。


三、StampedLock

1. StampedLock 的使用及特点

StampedLock 是 Java 1.8 版本中提供的锁,主要支持三种锁模式:写锁、悲观读锁、乐观读。

其中写锁和悲观读锁跟 ReadWriteLock 中的写锁和读锁的概念类似。StampedLock 在使用的时候不一样,加锁的时候会返回一个参数,解锁的时候需要传入这个参数,示例如下:

public class StampedLockTest {
    private final StampedLock lock = new StampedLock();
    private int value;
    private void addValue(){
        long stamp = lock.writeLock();
        try {
            value += 1;
        }
        finally {
            lock.unlockWrite(stamp);
        }
    }
}

StampedLock 最主要的特点是支持“乐观读”,即当进行读操作的时候,并不是所有的写操作都被阻塞,允许一个线程获取写锁。乐观读的使用示例如下:

public class StampedLockTest {
    private final StampedLock lock = new StampedLock();
    private int value;
    private void getValue(){
        //乐观读,读入变量
        long stamp = lock.tryOptimisticRead();
        int a = value;
        //如果验证失败
        if (!lock.validate(stamp)){
            //升级为悲观读锁,继续读入变量
            stamp = lock.readLock();
            try {
                a = value;
            }
            finally {
                lock.unlockRead(stamp);
            }
        }
    }
}

需要注意的是,这里使用 validate() 方法进行验证,如果乐观读失败,则升级为悲观读锁,继续获取变量。

2. StampedLock 的注意事项

StampedLock 不支持重入,即不可反复获取同一把锁。

在使用 StampedLock 的时候,不要调用中断操作。如果需要支持中断,可以调用 readLockInterruptibly 和 writeLockInterruptibly 方法。




相关文章
|
3月前
|
存储 Java API
MinIO Java SDK 7.1.4 升级到 8.5.17 需要注意什么
现在我需要你帮我分析对比这个两个sdk在对外的接口设计上是否有不兼容的变更
284 5
|
Java Apache 开发工具
【Azure 事件中心】 org.slf4j.Logger 收集 Event Hub SDK(Java) 输出日志并以文件形式保存
【Azure 事件中心】 org.slf4j.Logger 收集 Event Hub SDK(Java) 输出日志并以文件形式保存
135 1
|
存储 Java API
【Azure 存储服务】Java Storage SDK 调用 uploadWithResponse 代码示例(询问ChatGTP得代码原型后人力验证)
【Azure 存储服务】Java Storage SDK 调用 uploadWithResponse 代码示例(询问ChatGTP得代码原型后人力验证)
114 0
|
Java 开发工具
通过Java SDK调用阿里云模型服务
在阿里云平台上,可以通过创建应用并使用模型服务完成特定任务,如生成文章内容。本示例展示了一段简化的Java代码,演示了如何调用阿里云模型服务生成关于“春秋战国经济与文化”的简短文章。示例代码通过设置系统角色为历史学家,并提出文章生成需求,最终处理并输出生成的文章内容。在实际部署前,请确保正确配置环境变量中的密钥和ID,并根据需要调整SDK导入语句及类名。更多详情和示例,请参考相关链接。
|
JSON Java API
【Azure API 管理】通过Java APIM SDK创建一个新的API,如何为Reqeust的Representation设置一个内容示例(Sample)?
【Azure API 管理】通过Java APIM SDK创建一个新的API,如何为Reqeust的Representation设置一个内容示例(Sample)?
|
存储 Java 开发工具
【Azure 存储服务】Java Azure Storage SDK V12使用Endpoint连接Blob Service遇见 The Azure Storage endpoint url is malformed
【Azure 存储服务】Java Azure Storage SDK V12使用Endpoint连接Blob Service遇见 The Azure Storage endpoint url is malformed
115 0
|
开发工具 数据安全/隐私保护
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
224 0
|
机器学习/深度学习 编解码 Java
阿里云视觉智能开放平台(VIAPI)人脸美颜Java SDK使用说明
本文介绍人脸美颜FaceBeauty的语法及示例。
1566 0
阿里云视觉智能开放平台(VIAPI)人脸美颜Java SDK使用说明
|
Java 开发工具 计算机视觉
阿里云智能视觉生产图像处理人像分割Java SDK使用说明
人像分割用于识别输入图像中的人体轮廓,与背景进行分离,返回分割后的前景人像图(4通道),适用于单人、多人、复杂背景、各类人体姿态等场景。本文介绍如何使用阿里云智能视觉生产图像处理人体分割Java SDK,包括SDK的安装方法及SDK代码示例。
2957 1
|
自然语言处理 安全 Java
阿里云智能语音交互实时语音识别Java SDK使用说明
实时语音识别功能提供了对长时间的语音数据流进行识别,适用于会议演讲、视频直播等长时间不间断识别的场景。。本文介绍如何使用阿里云智能语音服务提供的Java SDK,包括SDK的安装方法及SDK代码示例。
3647 0