java并发程序设计总结七:jdk的并发容器

来源:互联网 发布:宜兴俊知集团工资高吗 编辑:程序博客网 时间:2024/05/16 07:55

线程安全的HashMap:Collections.synchronizedMap()

Collections工具类中提供了一系列synchronizedXxx方法用于包装对应的集合对象成线程安全的。这里就介绍下HashMap:
public static <K,V>Map<K,V> synchronizedMap(Map<K,V> m);
该方法会生成一个名为synchronizedMap的Map,它使用了委托机制,将自己所有的Map相关的功能交给传入的Map实现,自己主要负责保证线程安全。synchronizedMap类的参考实现部分如下:
private static class synchronizedMap<K,V> implements Map<K,V>,Serializable{    private static final long serialVersionUID = ...L;    private final Map<K,V> m;    final Object mutex;}
synchronizedMap同步实现的核心是通过mutex这个对象,在synchronizedMap中所有的map相关功能操作都是通过同步mutex对象实现的,比如对于map.get方法,他的实现如下:
public V get(Object obj){    synchronized(mutex){        ..    }}
该map可以满足线程安全的要求,但是,他在多线程环境中的性能非常低:无论是对map的读取还是写入,都需要获得mutex锁,这回导致所有对map的操作都会进入等待状态,直到mutex锁可用。在多线程高并发的情况下我们需要寻求新的解决方案


线程安全的HashMap:concurrentHashMap

concurrentHashMap内部进一步细分了若干个小的HashMap,称之为段,一个concurrentHashMap内部默认情况下被细分为16个段(通过减少锁细粒度的方式来削弱多线程的锁竞争)
当concurrentHashMap中增加一个新的表项时,并不是将整个HashMap加锁,而是会根据hashcode得到该表项存放在哪一个段中,然后对该段进行加锁,并完成put操作。
使用concurrentHashMap获取size等全局信息的方法调用时,在其内部会获得每个段的相应数据,最后进行统计,这样就需要获得每个段的锁,最终的性能大大低于同步的HashMap


高效读写的队列:ConcurrentLikedQueue

jdk中提供了一个ConcurrentLinkedQueue用来实现高并发的队列,从名字可以看出,这个队列使用链表作为其数据结构。作为一个链表,自然需要定义有关链表的节点,在ConcurrentLinkedQueue中,定义的节点Node核心如下:
private static class Node<E>{    volatile E item;    volatile Node<E> next;}
在ConcurrentLinkedQueue内部有两个重要的字段,head/tail,分别表示链表的头部和尾部,他们都是Node类型。其中tail尾节点最为特殊,一般来说,我们都是期望每次tail都是指向链表的末尾,但实际上,tail的更新不是及时的,每次更新会滞后跳跃一个元素。


高效读取:CopyOnWriteArrayList

之前博客说到ReadWriteLock读写分离锁,其内部分别为读和写定义了两个锁:writeLock和readLock,他实现了读读和读写相互之间不会发生等待的事件;这里使用CopyOnWriteArrayList同样实现了读读和读写之间不会进行同步等待,不过他的内部实现不是通过为读和写分别设置两个锁,它的实现是通过在进行写入操作时,进行一次自我复制,写完之后,再将修改完后的副本替换原来的数据,从而实现了写操作不会影响读了
有关读取的实现细节如下:
private volatile transient Object[] Array;public E get(int index){    return get(getArray(),index);}final Object[] getArray(){    return Array;}private E get(Object[] a,int index){    return (E) a[index];}
相比读取,其写入的实现就复杂一点:
public boolean add(E e){    final ReentrantLock lock = this.lock;    lock.lock();    try{        Object[] elements = getArray();        int len = elements.length;        Object[] newElements = Array.copyOf(elements,len+1);        newElements[len] = e;        setArray(newElements);        return true;    }    catch(Exception e){        ...    }    finally{        lock.unlock();    }}
从实现代码中可以看到,其复制了elements为一个新的newElements,然后对新的进行操作,最后通过set赋值,从而实现修改


随机数据结构:跳表

jdk并发包中,除了常用的哈希表外,还实现了一种有趣的数据结构–跳表,跳表是一种可以用来快速查找的数据结构。跳表使用的是随机算法,它的本质是同时维护了多个链表,并且链表是分层的,最底层的链表包含了所有的元素,自底向上每一层链表都是下面一层链表的子集,一个元素插入到哪些层是完全随机的。
虽然跳表中的元素位置都是随机的,使用随机算法来分配,但是跳表中的所有链表的元素都是有序的,在跳表中查找一个元素,首先从顶层的链表中查找,然后一层一层往下找,在跳表中的查询操作的时间复杂度是O(logn)。
在并发数据结构中,jdk使用跳表来实现一个map
实现这一数据结构的类是ConcurrentSkipListMap,它继承了AbstractMap,对跳表的操作和HashMap类似,不同的在于,对跳表的遍历输出是有序的


参考文献

java高并发程序设计第三章
阅读全文
0 0
原创粉丝点击