• Java并发(五):并发,迭代器和容器


      在随后的博文中我会继续分析并发包源码,在这里,得分别谈谈容器类和迭代器及其源码,虽然很突兀,但我认为这对于学习Java并发很重要;

    ConcurrentModificationException:

      JavaAPI中的解释:当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。一个线程通常不允许修改集合,而另一个线程正在遍历它。 一般来说,在这种情况下,迭代的结果是未定义的。 某些迭代器实现(包括由JRE提供的所有通用集合实现的实现)可能会选择在检测到此行为时抛出此异常。 这样做的迭代器被称为"及时失败"迭代器,当他们发现容器在迭代时被修改时,就会报异常;它称不上时一种处理机制,而是一种预防机制,只能作为并发问题的预警指示器.

    迭代器与容器:

      Vector这个"古老"的容器类相信大家很熟悉了,他是线程安全的没错,我们利用elements()方法遍历,不会出现线程安全的问题:

     1 /**
     2   *  JDK1.8源码
     3 */
     4 
     5 /**
     6      * Returns an enumeration of the components of this vector. The
     7      * returned {@code Enumeration} object will generate all items in
     8      * this vector. The first item generated is the item at index {@code 0},
     9      * then the item at index {@code 1}, and so on.
    10      *
    11      * @return  an enumeration of the components of this vector
    12      * @see     Iterator
    13      */
    14     public Enumeration<E> elements() {
    15         return new Enumeration<E>() {
    16             int count = 0;
    17 
    18             public boolean hasMoreElements() {
    19                 return count < elementCount;
    20             }
    21 
    22             public E nextElement() {
    23                 synchronized (Vector.this) {
    24                     if (count < elementCount) {
    25                         return elementData(count++);
    26                     }
    27                 }
    28                 throw new NoSuchElementException("Vector Enumeration");
    29             }
    30         };
    31     }
    JDK1.8源码: AbstractCollection (been extends by Vector)

      但当我们利用foreach进行迭代时,底层自动调用了iterator(),若我们不锁住容器,可能会报ConcurrentModificationException

     1 /**
     2      * Returns an iterator over the elements in this list in proper sequence.
     3      *
     4      * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     5      *
     6      * @return an iterator over the elements in this list in proper sequence
     7      */
     8     public Iterator<E> iterator() {
     9         return new Itr();
    10         //JDK1.8写成了一个内部类的形式返回迭代器
    11     }
    12 
    13     /**
    14      * An optimized version of AbstractList.Itr
    15      */
    16     private class Itr implements Iterator<E> {
    17         int cursor;       // index of next element to return
    18         int lastRet = -1; // index of last element returned; -1 if no such
    19         int expectedModCount = modCount;
    20 
    21         Itr() {}
    22 
    23         public boolean hasNext() {
    24             return cursor != size;
    25         }
    26 
    27         //观察下面的代码可以发现,每次迭代一次都要检查容器长度是否改变
    28         @SuppressWarnings("unchecked")
    29         public E next() {
    30             checkForComodification();
    31             int i = cursor;
    32             if (i >= size)
    33                 throw new NoSuchElementException();
    34             Object[] elementData = ArrayList.this.elementData;
    35             if (i >= elementData.length)
    36                 throw new ConcurrentModificationException();
    37             cursor = i + 1;
    38             return (E) elementData[lastRet = i];
    39         }
    40 
    41         public void remove() {
    42             if (lastRet < 0)
    43                 throw new IllegalStateException();
    44             checkForComodification();
    45 
    46             try {
    47                 ArrayList.this.remove(lastRet);
    48                 cursor = lastRet;
    49                 lastRet = -1;
    50                 expectedModCount = modCount;
    51             } catch (IndexOutOfBoundsException ex) {
    52                 throw new ConcurrentModificationException();
    53             }
    54         }
    55 
    56         @Override
    57         @SuppressWarnings("unchecked")
    58         public void forEachRemaining(Consumer<? super E> consumer) {
    59             Objects.requireNonNull(consumer);
    60             final int size = ArrayList.this.size;
    61             int i = cursor;
    62             if (i >= size) {
    63                 return;
    64             }
    65             final Object[] elementData = ArrayList.this.elementData;
    66             if (i >= elementData.length) {
    67                 throw new ConcurrentModificationException();
    68             }
    69             while (i != size && modCount == expectedModCount) {
    70                 consumer.accept((E) elementData[i++]);
    71             }
    72             // update once at end of iteration to reduce heap write traffic
    73             cursor = i;
    74             lastRet = i - 1;
    75             checkForComodification();
    76         }
    77 
    78         final void checkForComodification() {
    79             if (modCount != expectedModCount)
    80                 throw new ConcurrentModificationException();
    81         }
    82     }    
    JDK1.8源码: Vector

       查看代码时可以发现,iterator()的内部类中提供的next,remove等方法都进行了迭代操作,这样的迭代被称为"隐藏迭代器";

      部分容器类的toString()方法会在调用StringBuilder的append()同时迭代容器,例如继承了AbstractSet的HashSet类,而AbstractSet又继承于AbstractCollection:

    //  String conversion
    
        /**
         * Returns a string representation of this collection.  The string
         * representation consists of a list of the collection's elements in the
         * order they are returned by its iterator, enclosed in square brackets
         * (<tt>"[]"</tt>).  Adjacent elements are separated by the characters
         * <tt>", "</tt> (comma and space).  Elements are converted to strings as
         * by {@link String#valueOf(Object)}.
         *
         * @return a string representation of this collection
         */
        public String toString() {
            Iterator<E> it = iterator();
            if (! it.hasNext())
                return "[]";
    
            StringBuilder sb = new StringBuilder();
            sb.append('[');
            for (;;) {
                E e = it.next();
                sb.append(e == this ? "(this Collection)" : e);
                if (! it.hasNext())
                    return sb.append(']').toString();
                sb.append(',').append(' ');
            }
        }
    JDK1.8源码: AbstractCollection

      隐藏的迭代器普遍存在,甚至出现在hashCode,equals,contains,remove等方法中,此处不再赘述;

    解决办法:

      考虑到并发安全性,我们不得不对容器类单独加锁,但同时我们又不希望加锁,那样太损耗性能了,还存在线程饥饿和死锁的风险,极大降低吞吐量和CPU利用率;

      作为有限的替代,我们可以考虑"克隆"容器,并在将其副本封闭起来进行迭代,避免了抛出ConcurrentModificationException,但在克隆过程中仍然要对容器加锁;显然,克隆容器存在系统开销,我们在选择这种方案时必须考虑的因素有:容器大小,每个元素上执行的工作,迭代操作相对于其它操作的调用频率,以及相应时间和系统吞吐率的需求,具体应用详见CopyOnWriteArrayList类.

    参考材料:

      Java并发编程实战

  • 相关阅读:
    Redis缓存穿透和雪崩
    Redis主从复制
    Redis发布订阅
    IO多路复用
    Synchronized解读
    日志导致jvm内存溢出相关问题
    tomcat及springboot实现Filter、Servlet、Listener
    MySQL主从复制针对trigger的特殊处理
    二、变量/常量/数据类型
    Ubuntu21.04 / Linux Mint20.2 安装 TradingView分析软件
  • 原文地址:https://www.cnblogs.com/Joey44/p/10347732.html
Copyright © 2020-2023  润新知