并发编程(二)java.util.concurrent 并发容器类 Lock锁 原子类

本文自来我的公众号:  https://mp.weixin.qq.com/s/JWoN10ydbVJKET_Xf8NagA

 

下面来了解下Doug Lea老大爷的sdk并发包java.util.concurrent,了解这些常用的实用工具类后可以加深我们对并发编程的印象。

 

SDK concurrent并发包图

 

下面分3块,讲解其中的某些工具类,以及这些类的产生过程。这里并不详解类的用法。

 

一.  java.util.concurrent 容器类

java中容器主要分4大类:List、Set、Map、Queue。

 

我们经常使用的ArrayList,HashMap,其实它们不是线程安全的。如果要使用线程安全,怎么办呢?下面代码中,以ArrayList为例,

SafeArrayList内部持有一个ArrayList的实例list,所有访问list的方法都需要增加synchronized关键字,来保证原子性。

 

    SafeArrayList<T>{

      // 封装 ArrayList

      List<T> c = new ArrayList<>();

      synchronized

      T get(int idx){

        return c.get(idx);

      }

      synchronized

      void add(int idx, T t) {

        c.add(idx, t);

      }

       ... ...

    }

 

当然,我们写线程安全的集合时,不需要像上面代码那样写,因为java提供了线程安全的同步容器,如Vector,Hashtable。

但是,同步容器的缺点就是性能差,所有方法都用synchronized来保证互斥,串行度太高。

jdk1.5后,更高性能的容器诞生了,我们称之为:并发容器。

 

并发容器图

 

这里重点讲下 List容器- CopyOnWriteArrayList

 CopyOnWriteArrayList,写的时候会将共享变量复制一份出来,那么读操作安全无锁了,保证了性能。

具体做法是:CopyOnWriteArrayList内部维护了一个数组,成员变量transient volatile Object[] array就指向这个内部数组,读操作都是基于array进行的;如果有写操作,CopyOnWriteArrayList会将array复制一份:Arrays.copyOf,写操作就在这个新数组上进行,执行完之后,再将array指向这个新的数组。

所以CopyOnWriteArrayList适用于读多写少的场景,并且可以容忍读写短暂的不一致。

 

二.  java.util.concurrent.locks

 

SDK locks.* 图

 

1. ReentrantLock

 ReentrantLock,中文翻译可重入锁,线程可以重复获取同一把锁,因为内层函数也可能需要锁,所以也叫递归锁。ReentrantLock构造函数ReentrantLock(boolean fair),可以用来构造公平锁,或者非公平锁。所谓公平锁,指的是等待时间越长越可能获取锁。

 

2. Lock&Condition

Java内置的管程,如synchronized 只支持一个条件变量,而locks中的Lock&Condition支持多个,这是重要的区别 。比如,要实现一个阻塞队列就需要2个条件变量 :队列不空 和 队列不满。另外,内置管程的实现函数有wait、notify、notifyAll。而locks.Condition中是await、signal、signalAll。

lock&condition在其他领域都有使用,比如异步转同步调用(异步都是用线程来实现的。)

tcp协议本身是异步的,发送完RPC请求后,线程不等待RPC的响应结果。我们工作中用到的RPC同步都是框架帮我们实现了异步转同步,具体做法是:当 RPC 返回结果之前,阻塞调用线程,让调用线程等待;当 RPC 返回结果后,唤醒调用线程,让调用线程重新执行。比如Dubbo,DefaultFuture用lock, 经典的等待(await)-通知机制(signal),实现了线程阻塞。

 

3. ReadWriteLock

ReadWriteLock 读写锁,适用于读多写少的场景,读写锁遵循三个基本原则:

 a. 允许多个线程同时读共享变量;

 b. 只允许一个线程写共享变量;

 c. 如果正在执行一个写线程,禁止其他线程读操作。

 

正因为允许多个线程同时读,所以性能比互斥锁(synchronized)好。 并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。ReentrantReadWriteLock支持锁的降级,但不支持锁的升级,即同一个线程中,在没有释放读锁的情况下,不能去申请写锁,否则会发生死锁。

 

4. StampedLock 

stampedLock 支持三种锁:

a. 写锁

b. 悲观读锁

c. 乐观读,无锁机制, 最后用stamp验证有没有被写过,如果有,继续循环读直至stamp没有被修改,或者可升级成悲观读锁

 

可以看出,因为有乐观读,所以stampedLock比ReetrantReadWriteLock性能更高,但不支持重入;且写锁,悲观读锁不支持Condition变量。

 

stampedLock读模板:

        final StampedLock sl = new StampedLock();

        // 乐观读

        long stamp = sl.tryOptimisticRead();

        // 读入方法局部变量

        ......

        // 校验 stamp

        if (!sl.validate(stamp)){

          // 升级为悲观读锁

          stamp = sl.readLock();

          try {

            // 读入方法局部变量

            .....

          } finally {

            // 释放悲观读锁

            sl.unlockRead(stamp);

          }

        }

        // 使用方法局部变量执行业务操作

        ......

 

stampedLock写模板:

        long stamp = sl.writeLock();

        try {

          // 写共享变量

          ......

        } finally {

          sl.unlockWrite(stamp);

        }

 

三.  java.util.concurrent.atomic

 

SDK atomic.* 图

 

并发主要的问题其实是可见性、原子性和有序性的问题。可见性和有序性问题可以用volatile来解决,原子性问题可以采用互斥锁方案。那java sdk有没有提供简单的方案来解决原子性问题呢?

有,Java sdk提供了一系列的原子类,是一种无锁方案,完全没有加锁、解锁的性能消耗,同时还能保证互斥性。

那无锁方案的实现原理是什么呢? 很简单,硬件支持而已。CPU为了解决并发问题,提供了CAS指令,即Compare And Swap(Set)。作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。

 

CAS 指令包含 3 个参数:共享变量的内存地址 A、用于比较的值(预期) B 和共享变量的新值 C;

并且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。

所以,CAS 适合冲突较少的情况,如果太多线程在同时自旋,即A一直不等于B,那么长时间循环会导致 CPU 开销很大。

 

下面给出一段CAS使用的经典范例代码片段:

 

        public final long getAndAddLong(

          Object o, long offset, long delta){

          long v;

          do {

            // 读取内存中的值

            v = getLongVolatile(o, offset);

          } while (!compareAndSwapLong(

              o, offset, v, v + delta));

          return v;

        }

        // 原子性地将变量更新为 x

        // 条件是内存中的值等于 expected

        // 更新成功则返回 true

        native boolean compareAndSwapLong(

          Object o, long offset, 

          long expected,

          long x);

 

Java SDK 并发包里提供的原子类内容很丰富,分五个类别:基本数据类型、对象引用类型、数组、对象属性更新器和累加器。

原子类图

 

需要注意的是:对象引用类型中,对象引用的更新需要重点关注ABA问题,即A变成了B之后再变回A。解决方法是加版本号,类似于乐观锁。

例如,AtomicStampedReference实现的CAS方法就增加了版本号参数。

 

Java SDK提供的原子类大部分都实现了 compareAndSet() 方法,

但是原子类的方法都是针对一个共享变量的,使用场景有限,如果你需要解决多个变量的原子性问题,建议还是使用互斥锁方案。

 

<think>好的,我现在需要回答用户关于Java并发java.util.concurrent中常用及其使用场景的问题,要求分组介绍并逐个详解每个。用户还提供了之前的对话历史和一些参考资料,我需要确保回答准确且符合用户需求。 首先,回顾用户的问题,他希望按照分组的方式详细介绍每个组下的具体,包括使用场景。之前的回答中,用户提到ThreadLocal如何避免内存泄漏,而现在的转向是并发包中的其他。用户提供的引用内容提到了AtomicBoolean、ConcurrentHashMap、CountDownLatch、CyclicBarrier等,这些应该作为重点。 接下来,我需要将java.util.concurrent中的常用进行分组。常见的分方式包括原子并发容器、同步器、线程池相关、等。根据用户提供的引用,AtomicBoolean属于原子ConcurrentHashMap属于并发容器,CountDownLatch和CyclicBarrier属于同步器,用户还提到了线程安全容器,这可能属于并发容器的一部分。 我需要确保每个分组涵盖主要的,并解释其用途和典型场景。例如,原子用于无的线程安全操作,并发容器ConcurrentHashMap提供高效的并发访问,同步器如CountDownLatch用于线程间的协调。 需要注意的是,用户提到要参考之前的引用内容,比如引用[1]关于AtomicBoolean,引用[2]关于ConcurrentHashMap,引用[4]和[5]关于CountDownLatch和CyclicBarrier。这些信息需要整合到回答中,适当标注引用标记。 另外,要确保回答结构清晰,每个分组下逐一介绍,说明其功能、使用场景和示例代码(如果有的话)。同时,生成的相关问题需要与内容相关,帮助用户进一步理解。 检查是否有遗漏的重要,比如线程池(Executor框架)、(ReentrantLock)、阻塞队列(BlockingQueue)等。用户可能期望全面的分组,所以需要涵盖这些内容。 最后,按照用户要求使用中文,行内公式用$...$,独立公式用$$...$$,并添加引用标记。确保LaTeX语法正确,代码块格式正确,回答结构层次分明。</think>#### Java并发包 `java.util.concurrent` 常用分组详解 --- ### 一、原子(Atomic Classes) 提供无线程安全操作,基于CAS(Compare and Swap)实现,适用于计数器、状态标志等场景[^1]。 #### 1. **AtomicBoolean/AtomicInteger/AtomicLong** - **功能**:原子更新布尔值、整型、长整型。 - **使用场景**: - 计数器(如接口调用次数统计) - 状态标志(如开关控制) ```java AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); // 线程安全自增 ``` #### 2. **AtomicReference** - **功能**:原子更新对象引用。 - **使用场景**:需要原子性更新复杂对象(如缓存中的用户信息)。 #### 3. **LongAdder/DoubleAdder(Java 8+)** - **功能**:高并发下比 `AtomicLong` 性能更好,通过分段减少竞争[^1]。 - **使用场景**:高吞吐量的统计场景(如实时点击量统计)。 --- ### 并发容器Concurrent Collections) 提供线程安全的集合,支持高并发读写。 #### 1. **ConcurrentHashMap** - **功能**:分段机制,读操作无,写操作仅部分段[^2]。 - **使用场景**: - 高并发缓存(如商品库存缓存) - 替代 `HashTable`,提升多线程性能。 ```java ConcurrentHashMap<String, Integer> cache = new ConcurrentHashMap<>(); cache.computeIfAbsent("key", k -> loadFromDB(k)); // 线程安全的延迟加载 ``` #### 2. **CopyOnWriteArrayList/CopyOnWriteArraySet** - **功能**:写时复制,读操作无,写操作复制新数组。 - **使用场景**: - 读多写少的场景(如监听器列表) - 频繁遍历但极少修改的配置数据。 #### 3. **BlockingQueue(接口)** - **实现**:`ArrayBlockingQueue`、`LinkedBlockingQueue`、`PriorityBlockingQueue`。 - **功能**:阻塞式队列,支持生产者-消费者模型。 - **使用场景**: - 任务队列(如线程池任务缓冲) - 流量削峰(突发请求排队处理)。 --- ### 三、同步器(Synchronizers) 协调多线程的执行顺序或状态。 #### 1. **CountDownLatch** - **功能**:等待多个线程完成指定操作后触发事件[^4]。 - **使用场景**: - 并行任务完成后汇总结果(如多线程加载数据后启动系统)。 ```java CountDownLatch latch = new CountDownLatch(3); // 3个线程完成任务后调用 latch.countDown() latch.await(); // 主线程等待所有任务完成 ``` #### 2. **CyclicBarrier** - **功能**:多个线程相互等待,到达屏障后统一执行后续操作[^5]。 - **使用场景**: - 多阶段并行计算(如分阶段数据处理后合并)。 ```java CyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("所有线程就绪")); // 4个线程调用 barrier.await() 后触发回调 ``` #### 3. **Semaphore** - **功能**:控制同时访问资源的线程数量。 - **使用场景**: - 限流(如数据库连接池管理)。 ```java Semaphore semaphore = new Semaphore(10); // 允许10个线程并发访问 semaphore.acquire(); // 获取许可 semaphore.release(); // 释放许可 ``` --- ### 四、线程池框架(Executor Framework) 管理线程的生命周期和任务调度。 #### 1. **ExecutorService(接口)** - **常用实现**:`ThreadPoolExecutor`、`ScheduledThreadPoolExecutor`。 - **功能**:执行异步任务,支持定时/延迟任务。 - **使用场景**: - Web服务器请求处理 - 定时任务调度(如每日报表生成)。 ```java ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> processTask()); ``` #### 2. **Future/FutureTask** - **功能**:获取异步任务执行结果。 - **使用场景**: - 并行计算后获取结果(如多接口调用结果汇总)。 ```java Future<Integer> future = executor.submit(() -> calculate()); int result = future.get(); // 阻塞直到结果返回 ``` --- ### 五、与条件(Locks) 提供更灵活的机制,替代 `synchronized`。 #### 1. **ReentrantLock** - **功能**:可重入,支持公平与非公平。 - **使用场景**: - 替代 `synchronized`,支持更细粒度的控制。 ```java ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区操作 } finally { lock.unlock(); } ``` #### 2. **StampedLockJava 8+)** - **功能**:乐观读,减少读写竞争。 - **使用场景**:读多写少的高并发场景(如缓存系统)。 #### 3. **Condition** - **功能**:与配合使用,实现线程等待/通知机制。 - **使用场景**: - 生产者-消费者模型中的条件等待。 ```java Condition notEmpty = lock.newCondition(); // 消费者等待条件 while (queue.isEmpty()) notEmpty.await(); ``` --- ### 六、其他工具 #### 1. **CompletableFuture(Java 8+)** - **功能**:支持异步编程链式操作。 - **使用场景**:复杂的异步任务编排(如多服务调用组合)。 #### 2. **ForkJoinPool** - **功能**:分治任务并行处理。 - **使用场景**:递归任务分解(如大规模数组排序)。 --- ### 分组总结表 | 分组 | 核心 | 典型应用场景 | |---------------|-------------------------|----------------------------------| | 原子 | AtomicInteger, LongAdder | 计数器、状态标志 | | 并发容器 | ConcurrentHashMap | 高并发缓存 | | 同步器 | CountDownLatch, Semaphore | 多线程协作、限流 | | 线程池框架 | ThreadPoolExecutor | 任务调度、资源管理 | | 与条件 | ReentrantLock | 细粒度控制 | --- ### 相关问题 1. **如何选择 `CountDownLatch` 和 `CyclicBarrier`?** 2. **`ConcurrentHashMap` 在Java 8中如何优化分段?** 3. **为什么 `LongAdder` 在高并发下性能优于 `AtomicLong`?** 4. **如何避免 `ReentrantLock` 导致的死问题?** [^1]: AtomicBoolean 为我们提供了一个可以用原子方式进行读和写的布尔值。 : ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。 [^4]: CountDownLatch主要提供的机制是当多个线程都达到了预期状态或完成预期工作时触发事件。 [^5]: CyclicBarrier 的典型应用场景是用来等待并发线程结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值