`
uule
  • 浏览: 6305423 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

高并发下的数据结构List/Set/Map

阅读更多

高并发下的Java数据结构(List、Set、Map、Queue)

 

由于并行程序与串行程序的不同特点,适用于串行程序的一些数据结构可能无法直接在并发环境下正常工作,这是因为这些数据结构不是线程安全的。本节将着重介绍一些可以用于多线程环境的数据结构,如并发List、并发Set、并发Map等。

 

1.并发List

Vector 或者 CopyOnWriteArrayList 是两个线程安全的List实现,ArrayList 不是线程安全的。

因此,应该尽量避免在多线程环境中使用ArrayList。如果因为某些原因必须使用的,则需要使用Collections.synchronizedList(List list)进行包装。

 

示例代码:

 List list = Collections.synchronizedList(new ArrayList());
            ...
        synchronized (list) {
            Iterator i = list.iterator(); // 必须在同步块中
            while (i.hasNext())
                foo(i.next());
        }

     

CopyOnWriteArrayList 的内部实现与Vector又有所不同。顾名思义,Copy-On-Write 就是 CopyOnWriteArrayList的实现机制。即【当对象进行写操作时,复制该对象;若进行的读操作,则直接返回结果】,操作过程中不需要进行同步。

 

CopyOnWriteArrayList 很好地利用了对象的不变性,在没有对对象进行写操作前,由于对象未发生改变,因此不需要加锁。而【在试图改变对象时,总是先获取对象的一个副本(即复制List里的数组),然后对副本进行修改,最后将副本写回】

 

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

 

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

反观【CopyOnWriteArrayList 的get() 实现,并没有任何的锁操作】

 

在 add() 操作上,CopyOnWriteArrayList 的写操作性能不如Vector,原因也在于Copy-On-Write。

 

在读多写少的高并发环境中,使用 CopyOnWriteArrayList 可以提高系统的性能,但是,在写多读少的场合,CopyOnWriteArrayList 的性能可能不如 Vector。

 

核心实现:

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();
        }
    }
	

public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);

            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }

            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }	


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


    private E get(Object[] a, int index) {
        return (E) a[index];
    }

   	/**
     * Append the element if not present.
     * @param e element to be added to this list, if absent
     * @return <tt>true</tt> if the element was added
     */

    public boolean addIfAbsent(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Copy while checking if already present.
            // This wins in the most common case where it is not present

            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = new Object[len + 1];
            for (int i = 0; i < len; ++i) {
                if (eq(e, elements[i]))
                    return false; // exit, throwing away copy
                else
                    newElements[i] = elements[i];
            }

            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

 

2.并发Set

和List相似,并发Set也有一个 CopyOnWriteArraySet ,它实现了 Set 接口,并且是线程安全的。它的内部实现完全依赖于 CopyOnWriteArrayList ,因此,它的特性和 CopyOnWriteArrayList 完全一致,适用于 读多写少的高并发场合,在需要并发写的场合,则可以使用 Set s = Collections.synchronizedSet(Set<T> s)得到一个线程安全的Set。

 

示例代码:

Set s = Collections.synchronizedSet(new HashSet());
        ...
    synchronized (s) {
        Iterator i = s.iterator(); // 必须在同步块中
        while (i.hasNext())
            foo(i.next());
    }

 

其内部是基于CopyOnWriteArrayList实现的。

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    
    private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
	
public int size() {
        return al.size();
    }
	
	public boolean add(E e) {
        return al.addIfAbsent(e);
    }
}

 

3.并发Map

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

 

Collections的示例代码1:

 Map m = Collections.synchronizedMap(new HashMap());
            ...
        Set s = m.keySet();  // 不需要同步块
            ...
        synchronized (m) {  // 同步在m上,而不是s上!!
            Iterator i = s.iterator(); // 必须在同步块中
            while (i.hasNext())
                foo(i.next());
        }

 

1).为什么不能在高并发下使用HashMap?

因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

 

2).为什么不使用线程安全的HashTable?

 

【HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下】。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。【如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素】,所以竞争越激烈效率越低。

 

3).ConcurrentHashMap的优势

 

ConcurrentHashMap的内部实现进行了锁分离(或锁分段),所以它的锁粒度小于同步的 HashMap;同时,ConcurrentHashMap的 get() 操作也是无锁的。

 

锁分离:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

【有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段】,这需要【按顺序锁定所有段】,操作完毕后,又【按顺序释放所有段的锁】

 

 

4.并发Queue

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

 

ConcurrentLinkedQueue 是一个适用于高并发场景下的队列。它通过无锁的方式,实现了高并发状态下的高性能。通常,ConcurrentLinkedQueue 的性能要好于 BlockingQueue 。

与 ConcurrentLinkedQueue 的使用场景不同,【BlockingQueue 的主要功能并不是在于提升高并发时的队列性能,而在于简化多线程间的数据共享】

 

BlockingQueue 典型的使用场景是生产者-消费者模式,生产者总是将产品放入 BlockingQueue 队列,而消费者从队列中取出产品消费,从而实现数据共享。

 

BlockingQueue 提供一种读写阻塞等待的机制,即如果消费者速度较快,则 BlockingQueue 则可能被清空,此时消费线程再试图从 BlockingQueue 读取数据时就会被阻塞。反之,如果生产线程较快,则 BlockingQueue 可能会被装满,此时,生产线程再试图向 BlockingQueue 队列装入数据时,便会被阻塞等待,

 

 

5.并发Deque

在JDK1.6中,还提供了一种双端队列(Double-Ended Queue),简称Deque。Deque允许在队列的头部或尾部进行出队和入队操作。与Queue相比,具有更加复杂的功能。

 

Deque 接口的实现类:LinkedList、ArrayDeque和LinkedBlockingDeque。

 

它们都实现了双端队列Deque接口。其中【LinkedList使用链表实现了双端队列,ArrayDeque使用数组实现双端队列】。通常情况下,由于ArrayDeque基于数组实现,拥有高效的随机访问性能,因此ArrayDeque具有更好的遍性能。但是当队列的大小发生变化较大时,ArrayDeque需要重新分配内存,并进行数组复制,在这种环境下,基于链表的 LinkedList 没有内存调整和数组复制的负担,性能表现会比较好。但无论是LinkedList或是ArrayDeque,它们都不是线程安全的。

 

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

 

分享到:
评论

相关推荐

    Java开发常见问题总结.docx

    根据需求选择合适的集合类型(List, Set, Map等)。 对于并发环境,使用线程安全的集合类如ConcurrentHashMap、CopyOnWriteArrayList等。 避免在循环中修改集合,可能导致ConcurrentModificationException。 异常...

    Java八股文的面试题

    Java集合框架(JCF): Java集合框架提供了一套性能优化的接口和类,用于存储和处理数据集合,如List、Set、Map等。 多线程和并发: Java支持多线程编程,允许同时执行多个任务。Java中的并发编程机制包括线程、同步、...

    leetcode题库-sword_at_offer:Java、Python、算法、Spring等

    Set等数据结构 String,StringBuffer,StringBuilder 反射 对象的几种引用类型 线程 线程基础 原理 线程池 并发基础 并发的三大性质(原子性,可见性,顺序性) volatile / synchronized / CAS / Lock AQS 原子类 java....

    hibernate 体系结构与配置 参考文档(html)

    组件作为Map的索引(Components as Map indices ) 8.4. 组件作为联合标识符(Components as composite identifiers) 8.5. 动态组件 (Dynamic components) 9. 继承映射(Inheritance Mappings) 9.1. 三种策略 ...

    Hibernate实战(第2版 中文高清版)

     6.1 值类型的set、bag、list和map   6.1.1 选择集合接口   6.1.2 映射set   6.1.3 映射标识符bag   6.1.4 映射list   6.1.5 映射map   6.1.6 排序集合和有序集合  6.2 组件的集合   6.2.1 编写组件...

    NHibernate参考文档 2.0.0 chm

    5.1.17. map, set, list, bag 5.1.18. 引用(import) 5.2. NHibernate 的类型 5.2.1. 实体(Entities)和值(values) 5.2.2. 基本值类型 5.2.3. 自定义值类型 5.2.4. Any类型映射 5.3. SQL中引号包围的标识符 5.4. 模块...

    NHibernate中文帮组文档(2008.11月更新)

    5.1.17. map, set, list, bag 5.1.18. 引用(import) 5.2. NHibernate 的类型 5.2.1. 实体(Entities)和值(values) 5.2.2. 基本值类型 5.2.3. 自定义值类型 5.2.4. Any类型映射 5.3. SQL中引号包围的标识符 5.4. 模块...

    hibernate 框架详解

    组件作为Map的索引(Components as Map indices ) 9.4. 组件作为联合标识符(Components as composite identifiers) 9.5. 动态组件 (Dynamic components) 10. 继承映射(Inheritance Mappings) 10.1. 三种...

    java面试题

    map 成对的数据结构,键值必须具有唯一性 Servlet和CGI的区别? 答:Servlet与CGI的区别在于Servlet处于服务器进程中,它通过多线程方式允许其service方法,一个实例可以服务于多个请求,并且其实例一般不会被销毁...

    hibernate3.04中文文档.chm

    9.3. 组件作为Map的索引(Components as Map indices ) 9.4. 组件作为联合标识符(Components as composite identifiers) 9.5. 动态组件 (Dynamic components) 10. 继承映射(Inheritance Mappings) 10.1. 三种...

    Hibernate教程

    9.3. 组件作为Map的索引(Components as Map indices ) 9.4. 组件作为联合标识符(Components as composite identifiers) 9.5. 动态组件 (Dynamic components) 10. 继承映射(Inheritance Mappings) 10.1. 三种...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型  1.2.1 概念模型  1.2.2 关系数据...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型  1.2.1 概念模型  1.2.2 关系数据...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型  1.2.1 概念模型  1.2.2 关系数据...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型  1.2.1 概念模型  1.2.2 关系数据...

    hibernate 教程

    map, set, list, bag 5.1.16. 引用(import) 5.2. Hibernate 的类型 5.2.1. 实体(Entities)和值(values) 5.2.2. 基本值类型 5.2.3. 持久化枚举(Persistent enum)类型 5.2.4. 自定义值类型...

    hibernate

    map, set, list, bag 5.1.16. 引用(import) 5.2. Hibernate 的类型 5.2.1. 实体(Entities)和值(values) 5.2.2. 基本值类型 5.2.3. 持久化枚举(Persistent enum)类型 5.2.4. 自定义值类型...

    Hibernate+中文文档

    8.3. 组件作为Map的索引(Components as Map indices ) 8.4. 组件作为联合标识符(Components as composite identifiers) 8.5. 动态组件 (Dynamic components) 9. 继承映射(Inheritance Mappings) 9.1. 三种...

    Hibernate_3.2.0_符合Java习惯的关系数据库持久化

    8.3. 组件作为Map的索引(Components as Map indices ) 8.4. 组件作为联合标识符(Components as composite identifiers) 8.5. 动态组件 (Dynamic components) 9. 继承映射(Inheritance Mappings) 9.1. 三种...

Global site tag (gtag.js) - Google Analytics