JUC 并发集合:高效处理多线程数据共享的利器

在 Java 并发编程中,数据共享和线程安全是核心问题。Java 集合框架中的普通集合(如 ArrayList、HashMap)并非线程安全,在多线程环境下使用可能导致数据不一致或异常。为此,JDK 的 java.util.concurrent(JUC)包提供了一系列线程安全的并发集合,专门用于解决多线程环境下的数据操作问题。本文将详细介绍 JUC 并发集合的分类、特性及典型使用场景。

一、JUC 并发集合概述

JUC 并发集合位于 java.util.concurrent 包下,它们通过巧妙的并发设计(如分段锁、CAS 操作、Copy-On-Write 等)实现了高效的线程安全,避免了使用 synchronized 带来的性能开销。与传统同步集合(如 Vector、Hashtable)相比,JUC 并发集合具有更高的吞吐量和更好的并发性能。

根据功能,JUC 并发集合可分为以下几类:

  • ConcurrentMap:并发映射
  • ConcurrentCollection:并发集合
  • 阻塞队列(BlockingQueue)
  • 其他同步工具类

二、常用 JUC 并发集合详解

1. ConcurrentHashMap

ConcurrentHashMap 是 HashMap 的线程安全版本,也是 JUC 中最常用的并发集合之一。它在 JDK 1.7 中采用分段锁(Segment)机制,JDK 1.8 及以上则使用 CAS+synchronized 实现,进一步提升了并发性能。

特性

  • 支持高并发的读操作,读操作无需加锁
  • 写操作只锁定当前操作的节点,不影响其他节点的读写
  • 提供原子性的 putIfAbsent、remove 等操作

使用示例

java 复制代码
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        
        // 启动多个线程进行put操作
        for (int i = 0; i < 5; i++) {
            int threadNum = i;
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    String key = "key-" + j;
                    // 原子操作:如果key不存在则put,返回旧值
                    concurrentMap.putIfAbsent(key, threadNum);
                }
            }, "Thread-" + i).start();
        }
        
        // 等待所有线程完成
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("集合大小: " + concurrentMap.size());
    }
}

2. CopyOnWriteArrayList

CopyOnWriteArrayList 是 ArrayList 的线程安全变体,其核心思想是 "写时复制":当进行修改操作(add、set 等)时,会创建底层数组的副本,修改操作在副本上进行,完成后再将引用指向新数组。

特性

  • 读操作无锁,性能优异
  • 写操作需要复制数组,开销较大
  • 适合读多写少的场景

使用示例

java 复制代码
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        
        // 添加元素
        list.add("元素1");
        list.add("元素2");
        list.add("元素3");
        
        // 迭代过程中可以安全修改集合
        Iterator<String> iterator = list.iterator();
        new Thread(() -> {
            list.add("新元素");
            System.out.println("添加新元素后集合: " + list);
        }).start();
        
        // 迭代器遍历的是原数组,不会反映新添加的元素
        System.out.println("迭代器遍历结果:");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

3. BlockingQueue 接口

BlockingQueue 是支持阻塞操作的队列,当队列满时,入队操作会阻塞;当队列空时,出队操作会阻塞。它是实现生产者 - 消费者模式的理想选择。

常用实现类:

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的可选有界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列,每个 put 必须等待一个 take
  • PriorityBlockingQueue:支持优先级的无界阻塞队列
  • DelayQueue:支持延迟获取元素的无界阻塞队列

生产者 - 消费者模式示例

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    // 创建容量为10的有界阻塞队列
    private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
    
    public static void main(String[] args) {
        // 启动生产者线程
        new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    queue.put(i); // 队列满时会阻塞
                    System.out.println("生产者生产: " + i + ",队列大小: " + queue.size());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者").start();
        
        // 启动消费者线程
        new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    Integer value = queue.take(); // 队列空时会阻塞
                    System.out.println("消费者消费: " + value + ",队列大小: " + queue.size());
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者").start();
    }
}

4. ConcurrentLinkedQueue

ConcurrentLinkedQueue 是基于链表的无界非阻塞队列,它使用 CAS 操作保证线程安全,适合高并发场景下的队列操作。

特性

  • 无界队列,理论上可以无限添加元素
  • 非阻塞算法实现,高并发性能好
  • 不允许 null 元素

使用示例

java 复制代码
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueDemo {
    public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        
        // 入队操作
        queue.offer("元素1");
        queue.offer("元素2");
        queue.offer("元素3");
        
        // 出队操作
        System.out.println(queue.poll()); // 元素1
        System.out.println(queue.poll()); // 元素2
        
        // 查看队头元素
        System.out.println(queue.peek()); // 元素3
        
        // 遍历队列
        System.out.println("队列元素:");
        queue.forEach(System.out::println);
    }
}

5. 其他实用并发集合

  • ConcurrentSkipListMap:支持排序的并发 Map,类似于 TreeMap 的线程安全版本
  • ConcurrentSkipListSet:支持排序的并发 Set,内部基于 ConcurrentSkipListMap 实现
  • CopyOnWriteArraySet:基于 CopyOnWriteArrayList 实现的 Set,适合读多写少场景

三、JUC 并发集合与传统同步集合的对比

特性 JUC 并发集合 传统同步集合(如 Vector、Hashtable)
线程安全实现 采用 CAS、分段锁等机制 基于 synchronized 方法
并发性能 高,支持多线程同时读写 低,同一时间只允许一个线程操作
迭代器特性 弱一致性迭代器,不抛出 ConcurrentModificationException 快速失败迭代器,可能抛出 ConcurrentModificationException
适用场景 高并发读写场景 低并发或单线程场景

四、选择合适的 JUC 并发集合

选择 JUC 并发集合时,可参考以下原则:

  1. 判断是集合还是映射:需要键值对结构选择 ConcurrentHashMap 或 ConcurrentSkipListMap;需要线性结构选择相应的集合类。

  2. 考虑读写比例

    • 读多写少:优先选择 CopyOnWriteArrayList、CopyOnWriteArraySet
    • 读写均衡或写操作较多:选择 ConcurrentLinkedQueue、ConcurrentHashMap 等
  3. 是否需要阻塞功能:实现生产者 - 消费者模式时,优先选择 BlockingQueue 的实现类。

  4. 是否需要排序:需要排序功能时选择 ConcurrentSkipListMap 或 ConcurrentSkipListSet。

  5. 是否有界:对队列大小有限制时选择 ArrayBlockingQueue;无限制时可选择 LinkedBlockingQueue 或 ConcurrentLinkedQueue。

五、总结

JUC 并发集合为多线程环境下的数据操作提供了高效、安全的解决方案。它们通过精心设计的并发控制机制,在保证线程安全的同时,最大限度地提高了并发性能。

掌握 JUC 并发集合的特性和适用场景,能够帮助我们在实际开发中选择合适的集合类型,编写高效、健壮的并发程序。在使用时,应根据具体的业务场景和性能需求,合理选择最适合的并发集合,以达到最佳的程序性能。

相关推荐
小菜全4 小时前
基于若依框架开发WebSocket接口
java·javascript·maven·mybatis·html5
瓯雅爱分享4 小时前
Java搭建高效后端,Vue打造友好前端,联合构建电子采购管理系统,实现采购流程电子化、自动化,涵盖采购全周期管理,功能完备,附详细可运行源码
java·mysql·vue·软件工程·源代码管理
太白IT记4 小时前
form表达和实体类通常有什么不同
java·状态模式
架构师沉默4 小时前
同事查日志太慢,我现场教他一套 grep 组合拳
java·后端·架构
小李小李无与伦比4 小时前
MinerU环境部署——PDF转Markdown
开发语言·python·深度学习·conda
Seven974 小时前
剑指offer-27、字符串的排列
java
编码浪子4 小时前
趣味学RUST基础篇(String)
开发语言·后端·rust
编码浪子4 小时前
趣味学RUST基础篇(HashMap)
开发语言·后端·rust