【转】Fail-Safe,Fail-Fast Iterators in Java and ConcurrentModificationException

原文链接:Fail-Safe,Fail-Fast Iterators in Java and ConcurrentModificationException

In this article ,I would try to explain FAIL-FAST and FAIL-SAFE behavior of iterators.

Let us first try to understand what is Iterator?

Iterator is an interface defined in java.util package,with following methods declared in it :

boolean hasNext();
E next();
void remove();

For details on above methods,please refer java docs link java.util.Iterator<E>

Various collections in collection framework has implemented this iterator interface to provide facility to iterate over the elements of those collections.So as we can see from methods provided by Iterator interface,using hasNext method we can check if there are more elements in the collection and using next method we can actually get the next element in the collection.So these two methods are the backbone of Iterator implementation.

Lets see now what is Fail-fast and Fail-safe iterator.

**1. Fail-Fast Iterators
**

**What is the meaning of fail fast
**
When iterating over the elements of the collection,if there is some structural modification in the collection after the iterator is created except through remove method of collection,fail-fast iterator will fail immediately by throwing concurrentModificationException.

By structural modification,it means addition or deletion of elements from collection and not just reading or replacing of element.

Lets take an example of ArrayList. As we will see in below examples that concurrentModificationException will be thrown only when elements are added (using add() method) or deleted(using remove() method).However if we are simply replacing an element of an arrayList with another element(using set() method),it is not considered as structural modification and no concurrentModificationException will be thrown.

**From Java Docs(java.util.ArrayList)
**

"The iterators returned by this class’s iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a concurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throwConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs."

**ConcurrentModificationException in single Threaded Environment
**

a) ConcurrentModificationException on adding element in ArrayList while iterating

In the below example,we will try to add element in an ArrayList while we are iterating over it.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestFailfastIterator {
     public static void main(String[] args) throws Exception {
                List<String> list = new ArrayList<String>();
                 list.add("a");
                 list.add("b");
                 list.add("c");
                 Iterator<String> itrList = list.iterator();
                  while(itrList.hasNext()){
                            String element = itrList.next();
                            System.out.println(element);
                            if("a".equalsIgnoreCase(element)){
                                  list.add("d");  // We are adding element while iterating.
                            }
                 }
      }
}

Output :
a Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.gb.failFastIterator.TestFailfastIterator.main(TestFailfastIterator.java:16)

Probable Solution to avoid ConcurrentModificationException in case of a)

Assuming that in a) , developer wants to add another element("d") to the list on fulfilling some condition,in our case when element in the existing list is "a".This can be achieved as below by adding new elements in new ArrayList and then after iterator has completed,this sublist can be added (appended) at the end of existing list.

import java.util.ArrayList;
import java.util.Iterator;

public class TestFailfastIterator {
 public static void main(String[] args) throws Exception {
  List<String> list = new ArrayList<String>();
                list.add("a");
                list.add("b");
                list.add("c");
                list.add("a");
                List<String> subList = new ArrayList<String>();                 
                Iterator<String> itrList = list.iterator();
                while(itrList.hasNext()){
                        String element = itrList.next();
                        System.out.println(element);
                        if("a".equalsIgnoreCase(element)){
                                subList.add("d");
                        }
                }
                 list.addAll(subList);
                 System.out.println("Added sublist to end of original list");
                  for(String str : list){
                       System.out.println(str);
                  }
        }
}

Output :
a
b
c
a
Added sublist to end of original list
a
b
c
a
d
d
**
b) ConcurrentModificationException on removing element using remove method of ArrayList while iterating
**

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestFailfastIterator {
        public static void main(String[] args) throws Exception {
                List<String> list = new ArrayList<String>();
                list.add("a");
                list.add("b");
                list.add("c");
                list.add("a");
                Iterator<String> itrList = list.iterator();
                while(itrList.hasNext()){
                        String element = itrList.next();
                        System.out.println(element);
                        if("a".equalsIgnoreCase(element)){
                                list.remove("a");
                        }
                }
        }
}

Output :
a
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.gb.failFastIterator.TestFailfastIterator.main(TestFailfastIterator.java:16)

Probable Solution for ConcurrentModificationException in case of b)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestFailfastIterator {
    public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("a");
        List<String> subList = new ArrayList<String>();
        Iterator<String> itrList = list.iterator();
        while (itrList.hasNext()) {
            String element = itrList.next();
            System.out.println(element);
            if ("a".equalsIgnoreCase(element)) {
                subList.add("a");
            }
        }
        list.removeAll(subList);
        System.out.println("Removed elements of sublist from original list");
        for (String str : list) {
            System.out.println(str);
        }
    }
}

Output :
a
b
c
a
Removed elements of sublist from original list
b
c
**
Another Solution for b) and a better solution,as it need not create another ArrayList object.No ConcurrentModificationException is thrown on removing element while iterating using remove method of Iterator
**
If we remove element using Iterator's remove method,it will not throw concurrentModificationexception.Basically remove() method removes the element which is last returned by the next() method from the underlying ArrayList.

But why there is partiality between ArrayList's remove and Iterator's remove method.Why ArrayList's remove is throwing concurrentModificationException however Iterator's remove is not.
The proable reason that I can think of is that purpose of throwing concurrentModificationException is that ,once an Iterator has been taken on ArrayList(collection),then during the iteration it should be only Iterator itself which can do manipulation on the ArrayList ,no outside manipulation(write operartion) is allowed concurrently(at same time).

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestFailfastIterator {
    public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("a");
        Iterator<String> itrList = list.iterator();
        while(itrList.hasNext()){
            String element = itrList.next();
            System.out.println(element);
            if("a".equalsIgnoreCase(element)){
                itrList.remove();
            }
        }
        System.out.println("After removing elements by remove() method of Iterator");
        for(String str : list){
            System.out.println(str);
        }
    }
}

Output :
a
b
c
a
After removing elements by remove() method of Iterator
b
c

ConcurrentModificationException in Multi threaded environment
Here in the below program thread 2 is adding an element to ArrayList instance when it is being iterated by thread 1.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class CMExceptionMultithreaded {
    private static final List<String> list = new ArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Iterator<String> itrList = list.iterator();
                while (itrList.hasNext()) {
                    String str = itrList.next();
                    System.out.println("Thread1:" + str);
                }
            }
        });
        t1.start();
        //update list in second thread
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread 2");
                list.add("e");
            }
        });
        t2.start();
    }
}

Output :

Thread1:a
Thread1:b
Thread1:c
Thread 2
Thread1:d
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.gb.failFastIterator.CMExceptionMultithreaded$1.run(CMExceptionMultithreaded.java:20)
at java.lang.Thread.run(Thread.java:662)
**
Solution for ConcurrentModificationException in multi threaded environment
Solution 1:
Synchronize the code
**

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class CMExceptionMultithreaded {
    private static List<String> list = new ArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                list = Collections.synchronizedList(list);
                synchronized (list) {
                    Iterator<String> itrList = list.iterator();
                    while (itrList.hasNext()) {
                        String str = itrList.next();
                        System.out.println("Thread1:" + str);
                    }
                }
            }
        });
        t1.start();
        //update list in second thread
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                list = Collections.synchronizedList(list);
                synchronized (list) {
                    list.add("e");
                    Iterator<String> itrList = list.iterator();
                    while (itrList.hasNext()) {
                        String str = itrList.next();
                        System.out.println("Thread2:" + str);
                    }
                }
            }
        });
        t2.start();
    }
}

Output :
Thread1:a
Thread1:b
Thread1:c
Thread1:d
Thread2:a
Thread2:b
Thread2:c
Thread2:d
Thread2:e
**
Solution 2:
Use thread safe implementation of ArrayList which is CopyOnWriteArrayList
**
We can use CopyOnWriteArrayList which is thread safe implementation of List interface.It achieves its thread safe nature due the reason that whenever we take Iterator over collection,it keeps snapshot or copy of it at that time and works on that copy instead of the original collection.So if tw

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CMExceptionMultithreaded {
    private static List<String> list = new CopyOnWriteArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Iterator<String> itrList = list.iterator();
                while (itrList.hasNext()) {
                    String str = itrList.next();
                    System.out.println("Thread1:" + str);
                }

            }
        });
        t1.start();
        //update list in second thread
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                list.add("e");
                Iterator<String> itrList = list.iterator();
                while (itrList.hasNext()) {
                    String str = itrList.next();
                    System.out.println("Thread2:" + str);
                }

            }
        });
        t2.start();
    }
}

As you can see from above output that after Thread 1 iterated over list till element "c", thread 2 started ,which means it added element "e" to the list,but when Thread 1 resumed its iteration it did not print element "e" ,as Thread 1 and Thread 2 are having separate copies of list.

Note : concurrentHashMap and CopyOnWriteHashSet are thread safe implementations of HashMap and HashSet respectively.Basicallly all the classes in java.util.concurrent package are thread safe.

When concurrentModificationException will not be thrown

**Replacing element using set method and not adding or deleting will not throw concurrentModificationException,as its not structural modification and is not changing size.
**

As we discussed earlier in this article,that concurrentModificationException will be thrown only when there is structural change in the collection such that it changes(Increases or decreases) the size of collection.Mere replacing the element with another element or only iterating over collection will never throw exception.

Lets check with an example.

import java.util.ArrayList;
import java.util.Iterator;

public class TestFailfastIterator {
    public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        Iterator<String> itrList = list.iterator();
        while (itrList.hasNext()) {
            System.out.println(itrList.next());
            list.set(1, "d");
        }
    }
}

Output :
a
d
c

How Iterator knows that there has been structural modification in the ArrayList

For that we need to understand hierarchy of ArrayList,which can be seen in below diagram.ArrayList class inherits from abstract class AbstractList.This AbstractList class has a variable modCount,which is declared as below.If you get time ,you can read description about it as mentioned in Java doc comments below Or else I am going to explain briefly :)

/**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */

protected transient int modCount = 0;

Paste_Image.png

so ArrayList class inherits this modcount property from AbstractList class.Now whenever there is addition or removal of elements in the ArrayList,modcount will be increased by 1.

So say we added 4 elements to an ArrayList,in that case modCount will be 4.You can put the debug point as can be seen below and can inspect list after all four elements have been added.It will show you modcount 4.

Paste_Image.png

Now when we get Iterator over an ArrayList as below

list.iterator();

What iterator() method does is ,it will instantiate Itr class which is defined as inner class within ArrayList class and this Itr class has instance variable expectedModCount,which is initialized as below

int expectedModCount = modCount;

So which means that when we got iterator over an ArrayList, expectedModCount will also be 4.

Paste_Image.png

So far so good...

Say now while iterating we added another element(e) to ArrayList using add method of ArrayList,so what will happen?

modCount will increase to 5.

Paste_Image.png

expectedModCount of iteartor will still be 4.

Paste_Image.png

Now when we will call next() method of Iterator ,it first calls method checkForComodification(),which is defined as below:

Paste_Image.png

Now as modCount(5) != expectecdModCount(4),this will throw concurrentModificationException.

Moral of the story is ,while we are iterating over a collection,it should not be modified except by remove() method of Iterator,and if there are some modifications outside of Iterator,it will get to know that and it will throw concurrentModificationException.

Which are the collections which are having Fail Fast iterators

Iterators of all the collections within java.util package are fail fast.For example : ArrayList, LinkedList, HashSet, HashMap.

2) Fail-Safe Iterators

What is the meaning of Fail Safe

Fail-safe iterators don't throw concurrentModificationException like fail-fast Iterator, if Collection is modified structurally after taking iterator over collection. This is because they work on clone or copy of the original collection instead of original collection,so any modification done after iterator has been taken will be done on original collection,however the copy which iterator is referring is a snapshot taken when Iterator was taken over collection.As we can modify the collection after iterator is taken,so it is quite possible that iterator will be working on obsolete copy of the collection specially in multi threaded environment,which can produce some unwanted results later when the code is running in production.Also as every mutative operation(add,set and so on) leads to creation of fresh copy of underlying array,it will not be cost efffective to use copyOnWriteArrayList when you want to perform lots of mutative operations on list.

From Java Docs

"A thread-safe variant of ArrayList in which all mutative operations(add,set and so on) are implemented by making a fresh copy of the underlying array.This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw concurrentModificationException . The iterator will not reflect additions, removals, or changes to the list since the iterator was created. Element-changing operations on iterators themselves(remove,set and add) are not supported. These methods throw UnsupportedOperationException.All elements are permitted, including null."

Please check Solution 2 above for concurrentModificationException in Multithreaded enviorenment for example of Fail safe Iterator(CopyOnWriteArrayList).

Which are the collections which are having Fail Safe iterators

Iterators of all the collections within java.util.concurrent package are fail safe.For example : CopyOnWriteArrayList, CopyOnWriteHashSet, ConcurrentHashMap.
Hope this article was helpful to you.For more collections tutorials you can go through my below articles.Also I post on Google+ ,so you can add me in your circle to get updates
ArrayListFeatures every Java developer must know

Various ways to Iterate over commonly Used collections vizArrayList,HashSet,HashMap

ArrayList Write Operations explored

ArrayList Read Operations explored

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,874评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,151评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,270评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,137评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,116评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,935评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,261评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,895评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,342评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,854评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,978评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,609评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,181评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,182评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,402评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,376评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,677评论 2 344

推荐阅读更多精彩内容