Java并发基础:CopyOnWriteArraySet全面解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: CopyOnWriteArraySet类的优点在于能够实现无锁读取,确保高并发下的读取性能,同时,写操作通过复制底层数据来保证数据一致性,避免了多线程间的数据冲突,因此,它非常适合读多写少且对数据一致性要求较高的场景。

Java并发基础:CopyOnWriteArrayList全面解析 - 程序员古德

内容概要

CopyOnWriteArraySet类的优点在于能够实现无锁读取,确保高并发下的读取性能,同时,写操作通过复制底层数据来保证数据一致性,避免了多线程间的数据冲突,因此,它非常适合读多写少且对数据一致性要求较高的场景。

核心概念

假如,有一个在线购物平台,其中有一个功能是展示当前热门的商品,这个“热门商品”列表需要实时更新,因为用户的购买行为会不断改变商品的热门程度,同时,这个列表会被很多用户同时查看,因此它是一个读多写少的场景。

为了保证用户看到的热门商品列表总是最新的,每当有用户购买商品时,都需要更新这个列表,但是,由于更新操作可能会比较耗时(比如需要重新计算商品的热门度并排序),不希望这个更新过程阻塞用户查看列表的操作,此外,由于这个列表是实时更新的,也不能简单地锁定整个列表,因为这会导致用户看到的信息过时。

CopyOnWriteArraySet就非常适合解决这类场景的问题,可以将热门商品存储在一个CopyOnWriteArraySet中,每当有用户购买商品时,可以创建一个新的CopyOnWriteArraySet对象,将更新后的商品列表复制进去,然后再将这个新对象替换掉原来的对象,由于这个替换操作是原子的,所以用户要么看到的是更新前的列表,要么看到的是更新后的列表,不会出现中间状态。

即使有多个用户同时查看热门商品列表,他们也不会被更新操作阻塞,而且,由于每次更新都会创建一个新的对象,所以用户看到的总是最新的信息(至少是在他们查看的那一瞬间是最新的),这就是CopyOnWriteArraySet在处理读多写少、要求实时更新且不能阻塞读操作的场景中的典型应用。

如果写操作非常频繁,或者数据集非常大(因为每次更新都需要复制整个数据集),那么使用CopyOnWriteArraySet可能会带来性能问题。

CopyOnWriteArraySet的实现原理

CopyOnWriteArraySet类实现了Set接口,内部使用了一个CopyOnWriteArrayList来存储元素,当对集合进行修改(如add、remove等操作)时,它会先将底层数组复制一份,然后在新的数组上进行修改,修改完成后再将指向新数组的引用赋值给原数组,以此来保证集合的线程安全性,在这个过程中,读取操作不需要加锁,可以直接读取原数组中的数据,因此读取性能非常高。

由于写操作需要复制整个底层数组,所以CopyOnWriteArraySet在处理写操作时会有一定的性能开销,但是,在读多写少的场景下,这种开销是可以接受的,因为读取操作的高性能可以弥补写操作的性能损失。

代码案例

下面是一个简单的Java程序,演示了如何使用CopyOnWriteArraySet类,这段代码展示如何向集合中添加元素、检查元素是否存在以及遍历集合,同时,通过两个线程来模拟并发读写的情况,如下代码所示:

import java.util.concurrent.CopyOnWriteArraySet;  

public class CopyOnWriteArraySetExample {
   
     

    public static void main(String[] args) {
   
     
        // 创建一个CopyOnWriteArraySet实例  
        CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();  

        // 启动一个线程向集合中添加元素  
        new Thread(() -> {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                set.add("Item" + i);  
                try {
   
     
                    // 模拟一些延迟,使得输出更为明显  
                    Thread.sleep(100);  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        // 启动另一个线程来读取和打印集合中的元素  
        new Thread(() -> {
   
     
            while (set.size() < 10) {
   
     
                // 打印当前集合中的所有元素  
                System.out.println("Current set contents: " + set);  
                try {
   
     
                    // 等待一段时间,以便观察集合的变化  
                    Thread.sleep(200);  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
            // 最后打印完整的集合内容  
            System.out.println("Final set contents: " + set);  
        }).start();  
    }  
}

在代码中,创建了一个CopyOnWriteArraySet实例,并通过两个线程来操作它,第一个线程向集合中添加10个元素,每次添加后都会稍微延迟一段时间,第二个线程则不断地读取并打印集合的当前内容,直到集合的大小达到10为止。

由于CopyOnWriteArraySet的写操作(如add)会创建底层数组的一个新副本,因此即使在添加元素的过程中,读取操作(如第二个线程中的打印操作)也不会被阻塞,并且总是能够看到集合在某个时间点的一致性快照。

核心API

CopyOnWriteArraySet 类实现了 Set 接口,提供了线程安全的集合操作,它是通过在每次修改时复制底层数组来实现线程安全的,因此它特别适合读多写少的场景。以下是 CopyOnWriteArraySet 类中一些主要方法的含义:

1、构造方法

  • CopyOnWriteArraySet():创建一个空的集合。
  • CopyOnWriteArraySet(Collection<? extends E> c):创建一个包含指定集合中所有元素的集合。

2、查询操作

  • int size():返回集合中元素的数量。
  • boolean isEmpty():如果集合为空,则返回 true
  • boolean contains(Object o):如果集合包含指定的元素,则返回 true
  • Iterator<E> iterator():返回集合中元素的迭代器。
  • Object[] toArray():返回一个包含集合中所有元素的数组。
  • <T> T[] toArray(T[] a):返回一个包含集合中所有元素的数组,且类型与指定的数组相同。

3、修改操作

  • boolean add(E e):将指定的元素添加到集合中(如果尚未存在)。
  • boolean remove(Object o):从集合中移除指定的元素(如果存在)。
  • boolean addAll(Collection<? extends E> c):将指定集合中的所有元素添加到当前集合中。
  • boolean removeAll(Collection<?> c):从当前集合中移除指定集合中的所有元素(如果存在)。
  • boolean retainAll(Collection<?> c):仅保留当前集合中与指定集合中存在的元素。
  • void clear():移除集合中的所有元素。

4、集合视图操作(这些方法提供了不同视角的集合视图)

  • 由于 CopyOnWriteArraySet 是基于数组的,它并没有直接提供像 List 那样的顺序视图,但可以通过迭代器或数组来获取元素。

注意:由于 CopyOnWriteArraySet 的写操作(如 add, remove 等)是通过复制底层数组来实现的,因此在多线程环境下,这些操作可能相对较慢,但它不会阻塞读操作。

核心总结

Java并发基础:CopyOnWriteArrayList全面解析 - 程序员古德

CopyOnWriteArraySet 类是一个线程安全的集合,适用于读多写少的并发场景,它在读操作时无需加锁,性能高效,且能保证数据的一致性;因为写操作会复制底层数组,所以也不会阻塞读操作,提高了并发性能。但是它的写操作的开销较大,因为每次修改都需要复制整个数组,在高并发的写操作场景下,性能会受到影响,且会消耗更多内存,此外,CopyOnWriteArraySet 不支持一些 Set 接口的高级功能,如条件查询和批量操作。

技术方案选型上:在读多写少、数据大小适中且对一致性要求较高的场景中,推荐使用CopyOnWriteArraySet ;但在写操作频繁或数据量巨大的情况下,最好还是考虑其它的实现方案。

关注我,每天学习互联网编程技术 - 程序员古德

END!
END!
END!

往期回顾

精品文章

Java并发基础:ConcurrentSkipListMap全面解析

Java并发基础:ConcurrentSkipListSet全面解析!

Java并发基础:SynchronousQueue全面解析!

Java并发基础:ConcurrentLinkedQueue全面解析!

Java并发基础:Exchanger全面解析!

精彩视频

相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
24 2
|
11天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
61 6
|
3天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
21 4
|
12天前
|
Java 编译器 数据库连接
Java中的异常处理机制深度解析####
本文深入探讨了Java编程语言中异常处理机制的核心原理、类型及其最佳实践,旨在帮助开发者更好地理解和应用这一关键特性。通过实例分析,揭示了try-catch-finally结构的重要性,以及如何利用自定义异常提升代码的健壮性和可读性。文章还讨论了异常处理在大型项目中的最佳实践,为提高软件质量提供指导。 ####
|
12天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
21天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
9天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
32 9
|
6天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
12天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####

推荐镜像

更多
下一篇
无影云桌面