并发环境下的常用容器类

并发环境下的常用容器类

一:常用七大并发容器类

看一下juc下的容器类:

在这里插入图片描述

基本上collection下所有的容器类在并发环境下会有并发修改的异常

java.util.ConcurrentModificationException

1.ConcurrentHashMap

对应的非并发容器:HashMap 

目标:代替Hashtable、synchronizedMap,支持复合操作 

原理:JDK6中采用一种更加细粒度的加锁机制Segment“分段锁”,JDK8中采用CAS无锁算法。 

2.CopyOnWriteArrayList

对应的非并发容器:ArrayList 

目标:代替Vector、synchronizedList 

原理:利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。 

3.CopyOnWriteArraySet

对应的非并发容器:HashSet 

目标:代替synchronizedSet 

原理:基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。 

4.ConcurrentSkipListMap

对应的非并发容器:TreeMap 

目标:代替synchronizedSortedMap(TreeMap) 

原理:Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。 

5.ConcurrentSkipListSet

对应的非并发容器:TreeSet 

目标:代替synchronizedSortedSet 

原理:内部基于ConcurrentSkipListMap实现 

6.ConcurrentLinkedQueue

不会阻塞的队列 

对应的非并发容器:Queue 

原理:基于链表实现的FIFO队列(LinkedList的并发版本) 

7.LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue

对应的非并发容器:BlockingQueue 

特点:拓展了Queue,增加了可阻塞的插入和获取等操作 

原理:通过ReentrantLock实现线程安全,通过Condition实现阻塞和唤醒 

实现类: 
  •   LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列 	
    
  •   ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列 	
    
  •   PriorityBlockingQueue:按优先级排序的队列 	
    

二:并发环境中List接口下容器的替代容器

1.ArrayList

1). 出现异常:
//创建30个线程去操作list
        List<String>  list=new ArrayList<String>();
        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(1,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

异常:
java.util.ConcurrentModificationException
2). 解决方法:
2.1 使用Vector
List<String>  list=new Vector<>();

弊端:

3.1.1 古老的

才jdk1.0,基本上弃用
* @author  Lee Boynton
 * @author  Jonathan Payne
 * @see Collection
 * @see LinkedList
 * @since   JDK1.0
 */

3.1.2 每个方法都加锁,性能差

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

        return elementData(index);
    }

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

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

........

3.1.3 空间占用比ArrayList高

ArrayList和Vector初始值都是0,ArrayList每次扩容0.5,而Vector扩容1倍,所以ArrayList更加节省空间。Vector所有的功能ArrayList都已实现。

2.2 使用使用容器工具类下 Collections.synchronizedList(new ArrayList<>())/Collections.synchronizedSet(new HashSet<>())
List list1=new ArrayList();
List<String>  list=Collections.synchronizedList(list1);

Set set1=new HashSet();
Set set = Collections.synchronizedSet(set1);
2.3 使用写时复制 new CopyOnWriteArrayList()
使用:
List<String>  list=new CopyOnWriteArrayList<>();

Set<String> set = new CopyOnWriteArraySet<>();
3). 原理解析:

ArrayList、HashSet推荐改用CopyOnWrite容器

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

CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

2.LinkedList

1). 出现异常:
java.util.ConcurrentModificationException
2). 解决方法:
  1. Vector
  2. List list = Collections.synchronizedList(new LinkedList());
  3. LinkedList换成ConcurrentLinkedQueue
3). 原理解析:

推荐使用基于链表的非阻塞无界队列 ConcurrentLinkedQueue 。那么为什么ConcurrentLinkedQueue是如何保证线程安全的呢?由于ConcurrenLinkQueue是一个基于链表的FIFO队列,所以着重关注入队和出队的方法。

3.1 offer 在队列尾部添加一个元素

如果传递的参数是 null 则抛出 NPE 异常,否者由于 ConcurrentLinkedQueue 是无界队列该方法一直会返回 true。另外由于使用 CAS 无阻塞算法,该方法不会阻塞调用线程,其源码如下:

public boolean offer(E e) {
    //(1)e为null则抛出空指针异常
    checkNotNull(e);

   //(2)构造Node节点
    final Node<E> newNode = new Node<E>(e);

    //(3)从尾节点进行插入
    for (Node<E> t = tail, p = t;;) {

        Node<E> q = p.next;

        //(4)如果q==null说明p是尾节点,则执行插入
        if (q == null) {

            //(5)使用CAS设置p节点的next节点
            if (p.casNext(null, newNode)) {
                //(6)cas成功,则说明新增节点已经被放入链表,然后设置当前尾节点
                if (p != t)
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
        }
        else if (p == q)//(7)
            //多线程操作时候,由于poll操作移除元素后有可能会把head变为自引用,然后head的next变为新head,所以这里需要
            //重新找新的head,因为新的head后面的节点才是正常的节点。
            p = (t != (t = tail)) ? t : head;
        else
            //(8) 寻找尾节点
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

3.2. poll 在队列头部获取并且移除一个元素

如果队列为空则返回 nul

public E poll() {
    //(1) goto标记
    restartFromHead:

    //(2)无限循环
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {

            //(3)保存当前节点值
            E item = p.item;

            //(4)当前节点有值则cas变为null
            if (item != null && p.casItem(item, null)) {
                //(5)cas成功标志当前节点以及从链表中移除
                if (p != h) 
                    updateHead(h, ((q = p.next) != null) ? q : p);
                return item;
            }
            //(6)当前队列为空则返回null
            else if ((q = p.next) == null) {
                updateHead(h, p);
                return null;
            }
            //(7)自引用了,则重新找新的队列头节点
            else if (p == q)
                continue restartFromHead;
            else//(8)
                p = q;
        }
    }
 }

三:并发环境中Set接口下容器的替代容器

1.HashSet

1).出现异常:
java.util.ConcurrentModificationException
2).解决方法:
2.1使用使用容器工具类下Collections.synchronizedSet(new HashSet<>())
Set set1=new HashSet();
Set set = Collections.synchronizedSet(set1);
2.2 使用写时复制 new CopyOnWriteArrayList()
使用:
Set<String> set = new CopyOnWriteArraySet<>();
3).原理解析:

还是推荐使用CopyOnWriteArraySet。CopyOnWrite采用写时复制,读写分离,保证了线程安全而且比加锁效率高。

2.LinkedHashSet

LinkedHashSet 继承自HashSet,但是底层用了一个链表维护了取出和存入的顺序 。

1).出现异常:
java.util.ConcurrentModificationException
2).解决方法:
2.1使用使用容器工具类下Collections.synchronizedSet(new LinkedHashSet<>())
2.2使用写时复制CopyOnWriteArraySet(new LinkedHashSet<>())

因为CopyOnWriteArraySet构造方法有无参构造和有参构造,LinkedHashSet继承自HashSet.

public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }


public CopyOnWriteArraySet(Collection<? extends E> c) {
      if (c.getClass() == CopyOnWriteArraySet.class) {
          @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
              (CopyOnWriteArraySet<E>)c;
          al = new CopyOnWriteArrayList<E>(cc.al);
      }
      else {
          al = new CopyOnWriteArrayList<E>();
          al.addAllAbsent(c);
      }
    }

3.TreeSet

Set是无序的集合,而TreeSet是有序(这里的有序是元素根据自然排序规则或者定制排序的规则排序)的集合,所以如果是包装数据类型String,Integer等等不需要元素实现自然排序或者定制排序,其他的对象类型需要实现自然排序或者定制排序。但是在多线程的情况下TreeSet还是会出现并发修改异常。

1).出现异常:
java.util.ConcurrentModificationException
2).解决方法:

2.1 CopyOnWriteArraySet<>(new TreeSet<>()

2.2 Collections.synchronizedSet(new TreeSet<>())

2.3 new ConcurrentSkipListSet();

3).原理解析:

上面三种方式均可以保证在并发情况下实现TreeSet功能。推荐使用ConcurrentSkipListSet,内部基于ConcurrentSkipListMap实现 的,ConcurrentSkipListSet位于juc包下,底层是基于跳表的数据结构。关于跳表的数据结构可以看我的另一片文章。https://blog.csdn.net/ITgagaga/article/details/102843885。简单来ConcurrentSkipListMap中有三种类型节点Node、Index、HeadIndex,底层为Node单链表有序层(升序),其他层为基于Node层的Index层即索引层,可能有多个索引层。通HeadIndex维护索引层次,通过Index从最上层开始往下查找元素,一步步缩小查找范围,到了最底层Node单链表层,就只需要比较很少的元素就可以找到待查找的元素节点。关于ConcurrentSkipListMap的一些概念以及原理可以看我的文章https://blog.csdn.net/ITgagaga/article/details/108581340

四:并发环境中Map接口下容器的替代容器

1.HashMap

1.1 使用ConcurrentHashMap

1.2 使用Collections.synchronizedMap(new HashMap<>()

2.LinkedHashMap

2.1 使用Collections.synchronizedMap(new LinkedHashMap<>());

3.TreeMap

3.1 Collections.synchronizedMap(new TreeMap<>())

3.2 ConcurrentSkipListMap

4.HashTable

HashTable古老的,线程安全,可直接使用ConcurrentHashMap;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值