【Java程序性能优化 第一版】第四章(JDK并发数据结构)

本文介绍了JDK中几种常见的并发数据结构,包括线程安全的List、Set、Map及Queue等,重点对比了CopyOnWriteArrayList与Vector、CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue和LinkedBlockingDeque的实现原理与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                        4.3 JDK并发数据结构

  由于并行程序与串行程序的不同特点,适用于串行程序的一些数据结构可能无法直接在并发环境下工作,这是因为这些数据结构不是线程安全的

 4.3.1 并发List

  Vector或者CopyOnWriteArrayList是两个线程安全的List实现,ArrayList不是线程安全的,因此,应该尽量避免在多线程环境中使用ArrayList。如果因为某些原因必须使用的,则需要使用下面代码进行包装。

        Collections.synchronizedCollection(new ArrayList<>());

 CopyOnWriteArrayList的内部实现与Vector又有所不同。望文生义,Copy-On-Write就是CopyOnWriteArrayList的实现机制。即当对象进行写操作时,复制该对象;若进行的是读操作,则直接返回结果,操作过程中不进行同步。它很好地利用了对象的不变性,在没有对象进行写操作前,由于对象未发生改变,因此不需要加锁。而在试图改变对象时,总是先获取对象的一个副本,然后对副本进行修改,最后将副本写回

 这种实现方式的核心思想是减少锁竞争,从而提高在高并发时的读取性能,但是它却在一定程度上牺牲了写的性能。

  public E get(int index) {
        return get(getArray(), index);
    }

 Vector使用了同步关键字,所有的get()操作都必须先取得对象锁才能进行。在高并发的情况下,大量的锁竞争会拖累系统性能。

   public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

 虽然CopyOnWriteArrayList的读操作性能优越,但是,它的写操作却不尽人意,它的add方法如下:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);// 数组复制
            newElements[len] = e; // 修改副本
            setArray(newElements);// 写回副本
            return true;
        } finally {
            lock.unlock();
        }
    }

在每一次的add方法中,都是进行一次自我复制,同时,add也申请了锁,并不像get方法那样。相对地,Vector的add方法则要快捷很多。因此,在高并发且以读为主的应用场景中,CopyOnWriteArrayList要优于Vector,但是,当写操作为主的情况下,应该优先使用Vector.

CopyOnWriteArrayList如何做到线程安全的

CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。

当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。当元素在新数组添加成功后,将array这个引用指向新数组。CopyOnWriteArrayList的整个add操作都是在的保护下进行的。 
这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。

4.3.2 并发Set

   和List相似,并发Set也有一个CopyOnWriteArraySet,它实现了Set接口,并且是线程安全的。它的内部实现完全依赖于CopyOnWriteArrayList,因为他们的特性完全一致。

4.3.3 并发Map

   在多线程中使用Map,一般可以使用Collections的synchronizeMap()方法得到一个线程安全的Map。但是在高并发的情况下,这个Map的性能不是最优的。由于Map是使用相当频繁的一个数据结构,因此JDK中便提供了一个专门用于高并发的Map实现CuncurrentHashMap

4.3.4 并发Queue

   在并发队列上,JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列。不论哪种实现,都继承自Queue接口

   ConcurrentLinkedQueue是一个适用于高并发场景下的队列。它通过无锁的方式,实现了高并发下的高性能。通过它的性能要好于BlockingQueue。BlockingQueue的主要功能并不是在于提高高并发的队列性能,而在于简化多线程间的数据共享,它的性能低于COncurrentLinkedQueue

4.3.4 并发Dueue

  在JDK中,还提供了一种双端队列,简称Depue。它允许在队列的头部或者尾部进行出队和入队操作。与Queue相比,它们具有更加复杂的功能。

  LinkedBlockingDueue是一个线程安全的双端队列实现。可以说,它已经是最为复杂的一个队列实现。在内部实现中,LinkedBlockingDueue使用链表结构。每一个队列节点都维护一个前驱节点和一个后驱节点。LinkedBlockingDueue没有进行读写锁的分离,因此同一个时间只能有一个线程对其进行操作。因此在高并发的应用中,它的性能表现要远远低于LinkedBlockingQueue,更要低于CocurrentLinkedQueue。

   

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值