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源码 from 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源码 from 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源码: from AbstractCollection

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

解决办法:

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

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

参考材料:

  Java并发编程实战

原文地址:https://www.cnblogs.com/Joey44/p/10347732.html

时间: 2024-07-29 18:13:43

Java并发(五):并发,迭代器和容器的相关文章

深入浅出 Java Concurrency (17): 并发容器 part 2 ConcurrentMap (2)

本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈内部设计的原理和思想. 要谈ConcurrentHashMap的构造,就不得不谈HashMap的构造,因此先从HashMap开始简单介绍. HashMap原理 我们从头开始设想.要将对象存放在一起,如何设计这个容器.目前只有两条路可以走,一种是采用分格技术,每一个对象存放于一个格子中,这样通过对格子

java 并发(五)---AbstractQueuedSynchronizer

文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现是基于FIFO等待队列的. Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out

Java基础】并发 - 多线程

Java基础]并发 - 多线程 分类: Java2014-05-03 23:56 275人阅读 评论(0) 收藏 举报 Java 目录(?)[+] 介绍 Java多线程 多线程任务执行 大多数并发应用程序时围绕执行任务(task)进行管理的:所谓任务就是抽象的,离散的工作单元. 围绕执行任务来管理应用程序时,第一步是要指明一个清晰的任务边界.大多数应用服务器程序都选择了下面这个自然的任务辩解:单独的客户请求: 任务时逻辑上的单元: 任务 Runnable 表示一个任务单元(java.lang)

JAVA多线程和并发基础面试问答【转】

JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单 一进程.线程可以被称为轻量

17、JAVA多线程和并发基础面试问答

JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(se

[转] JAVA多线程和并发基础面试问答

JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(se

JAVA多线程和并发性知识点总结

转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46861397 上次我总结了一份JAVA 面向对象和集合知识点总结: http://blog.csdn.net/zhoubin1992/article/details/46481759 受到了博友们的激励,这次对JAVA多线程和并发性相关知识点进行总结,方便各位博友学习以及自己复习之用. 一. 什么是进程.线程?线程和进程的区别? 1. 进程 当一个程序进入内存运行时,即变成一个进

java之高并发与多线程

进程和线程的区别和联系 从资源占用,切换效率,通信方式等方面解答 线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元:而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务.在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程.下面,我们从调度.并发性. 系统开销.拥有资源等方面,来比较线程与进程. 1.调度 在传统的操作系统中,拥有资源的基本单位和独立调度.分派的基本单位都是进程.

Java多线程视频教程并发编程面试知识

课程目录:  1-1.并发编程入门到实战课程简介1-2.什么是并发编程1-3.并发编程的挑战之频繁的上下文切换1-4.并发编程的挑战之死锁1-5.并发编程的挑战之线程安全1-6.并发编程的挑战之资源限制2-1.进程与线程的区别2-2.线程的状态及其相互转换2-3.创建线程的方式(上)2-4.创建线程的方式(下)2-5.线程的挂起及其恢复2-6.线程的中断操作2-7.线程的优先级2-8.守护线程3-1.什么是线程安全性3-2.从字节码角度剖析线程不安全操作3-3.原子性操作3-4.深入理解sync