Java并发编程:第八章 并发集合

本文详细介绍了Java并发包中的CopyOnWriteArrayList,CopyOnWriteArraySet以及ConcurrentHashMap的原理、适用场景和性能特点,特别是CopyOnWriteArrayList的写时复制策略及其在读多写少场景的优势和局限。还涵盖了ConcurrentHashMap在JDK7和JDK8版本中的锁机制优化。

一、List

1、CopyOnWriteArrayList

CopyOnWriteArrayList是Java并发包java.util.concurrent中提供的一种线程安全的ArrayList。它的主要特性在于写时复制(Copy-On-Write)的并发策略,即在修改操作(如添加、修改、删除元素)时,不是直接修改原数组,而是先复制一份底层数组,然后在副本上进行修改操作,修改完成后再将原数组的引用指向新的数组。这种机制保证了修改操作的线程安全性,同时读取操作可以完全不用加锁,从而实现了读操作的高性能。

CopyOnWriteArrayList适用于读多写少的场景,比如缓存。当应用程序的读操作频率远高于写操作时,CopyOnWriteArrayList是一个很好的选择。因为每次修改操作都会创建一个底层数组的副本,从而避免了读取操作受到写入操作的干扰。同时,由于每个线程都在自己的副本上进行操作,因此不存在读取过程中数据被其他线程修改的问题,从而保证了数据的一致性。

然而,CopyOnWriteArrayList也存在一些缺点。由于写操作时需要拷贝数组,如果原数组的内容较多,可能会消耗较大的内存,并可能导致年轻代(Young Generation)或老年代(Old Generation)的垃圾回收(GC)。此外,如果数据量非常大,每次添加或修改元素都需要重新复制数组,性能开销可能会比较大。因此,在选择使用CopyOnWriteArrayList时,需要根据具体的应用场景和需求进行权衡。

总的来说,CopyOnWriteArrayList是一种适用于读多写少场景的线程安全ArrayList实现,通过写时复制的机制保证了数据的一致性和读取性能,但在写操作频繁或数据量较大的情况下可能存在性能问题。

CopyOnWriteArrayList 是一个线程安全的动态数组,非常适合用于读多写少的并发场景。下面是一个简单的示例,演示了 CopyOnWriteArrayList 的基本用法:

package chatpter07;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        // 创建一个CopyOnWriteArrayList实例
        List<String> list = new CopyOnWriteArrayList<>();

        // 在多线程环境下添加元素
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                list.add("Element from thread 1: " + i);
                System.out.println("Thread 1 added element: " + list.get(list.size() - 1));
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                list.add("Element from thread 2: " + i);
                System.out.println("Thread 2 added element: " + list.get(list.size() - 1));
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完毕
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 读取并打印列表中的元素
        System.out.println("Final list content:");
        for (String element : list) {
            System.out.println(element);
        }
    }
}

执行结果:

Thread 1 added element: Element from thread 1: 0
Thread 1 added element: Element from thread 1: 1
Thread 1 added element: Element from thread 1: 2
Thread 1 added element: Element from thread 1: 3
Thread 1 added element: Element from thread 1: 4
Thread 2 added element: Element from thread 2: 0
Thread 2 added element: Element from thread 2: 1
Thread 2 added element: Element from thread 2: 2
Thread 2 added element: Element from thread 2: 3
Thread 2 added element: Element from thread 2: 4
Final list content:
Element from thread 1: 0
Element from thread 2: 0
Element from thread 1: 1
Element from thread 1: 2
Element from thread 1: 3
Element from thread 1: 4
Element from thread 2: 1
Element from thread 2: 2
Element from thread 2: 3
Element from thread 2: 4

Process finished with exit code 0

二、Set

1、CopyOnWriteArraySet

继承于AbstractSet类,对应的基础容器为HashSet。其内部组合了一个CopyOnWriteArrayList对象,它核心操作是基于CopyOnWriteArrayList实现

2、concurrentSkipListSet

是线程安全的有序集合,对应的基础容器为TreeSet。它继承了AbstractSet,并实现NavigableSet接口。CurrentSkipListSet是通过CurrentSkipListMap实现的

三、Map

1、ConcurrentHashMap

对应的基础容器是HashMap,JDK7中采用一种更加细粒度的“分段锁”加锁机制,JDK8中采用CAS无锁算法

(1) JDK7

采用segment分段锁的方式实现,一个CurrentHashMap中包含一个segment数组,一个segment中包含一个HashEntry数组,HashEntry数组中每个元素是一个链表结构。当对hashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问,实现并发访问。
在这里插入图片描述

  • Segment源码
    在这里插入图片描述
    在这里插入图片描述
    • Segment是CurrentHashMap的一个内部主要的组成,继承自ReentrantLock。volatile修饰HashEntry<K,V>[] table,可以保证在数组扩容时的可见性。
    • volatile修饰HashEntry的数据value和下一个节点next,保证了多线程环境下数据获取时的可见性。

(2)JDK8

在JDK1.8中,ConcurrentHashMap已经抛弃了Segment分段锁机制,存储结构采用Node数组 + 链表+红黑树的组合方式,利用CAS + Synchronized来保证并发更新的安全。
在这里插入图片描述
在这里插入图片描述

  • 在锁的实现上,抛弃了原有的Segment分段锁,采用CAS + synchronized实现更加细粒度的锁。将锁的级别控制在了更细粒度的哈希桶数组元素级别,只需要锁住这个链表的头结点(或红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

特性

  • 不支持key或者value为null
    • 因为ConcurrentHashMap工作于多线程环境,如果ConcurrentHashMap.get(key) 返回为null,就无法判断值为null还是key不存在(即:存在二义性)。而单线程HashMap可以用containsKey(key)判断是否包含这个key。
    • 与HashMap迭代器是强一致性不同,ConcurrentHashMap迭代器是弱一致性
      • ConcurrentHashMap的迭代器创建后,就会按照哈希表结构遍历每个元素,但在遍历过程中,内部元素可能会发生变化,如果变化发生在已遍历过的部分,迭代器就不会反映出来,如果变化发生在未遍历部分,迭代器就会发现并反映出来,这就是弱一致性。
      • 这样迭代器线程可以使用原来老的数据,而写线程也可以并发的完成改变,保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

API介绍

  • get方法
    • 因为Node和HashEntry的元素value和指针next是用volatile修饰的,在多线环境下线程A修改节点的value或者新增节点的时候是对线程B可见的。

2、ConcurrentSkipListMap

对应的基础容器为TreeMap,其内部的SkipList(跳表)结构是一种可以代替平衡树的数据结构,默认是按照Key升序的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玉成226

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

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

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

打赏作者

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

抵扣说明:

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

余额充值