fail-fast机制(CopyOnWriteArrayList)

一、发现问题

这个机制在java集合中尤为常见。下面查看java结合根接口Collection

The default implementation creates a late-binding spliterator from the collections's {@code Iterator}.  
The spliterator inherits thefail-fast  properties of the collection's iterator.

也就说所有的集合中的迭代器都会有这个机制的检测。而且会抛出ConcurrentModificationException异常。

/**
 *This exception may be thrown by methods that have detected concurrent
 *modification of an object when such modification is not permissible.
 *For example, it is not generally permissible for one thread to modify a Collection
 *while another thread is iterating over it.
 */
 public class ConcurrentModificationException extends RuntimeException {
 }

下面演示这个异常:

package com.hfview.list;

import lombok.extern.slf4j.Slf4j;

import java.lang.invoke.VolatileCallSite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * //TODO 写注释
 *
 * @author: zhw
 * @since: 2019/3/13 14:04
 */
@Slf4j
public class FailFase {

    static List list = new ArrayList<>();

    public static void main(String[] args) throws Exception{



        ExecutorService executorService = Executors.newFixedThreadPool(10);

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                Iterator it = list.iterator();
                log.debug("iterator method it.hasNext():"+it.hasNext());
                while(it.hasNext()){
                    log.debug("iterator method:");
                    System.out.println(it.next());
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10000;i++){
                    list.add((i+1));
                }
                log.debug(" add method end");
            }
        });

        executorService.shutdown();
    }
}

fail-fast机制(CopyOnWriteArrayList)_第1张图片
在迭代的国中添加元素就会发生异常。

当时放生这个问题的时候我第一发应是并发的读写并发执行,那么调用Vector就行了。
然后我就

static List list = new Vector<>();

然而还是报错,打开源码我才发现,迭代器中有个检查方法(快速失败检查)

 final void checkForComodification() {
            if (modCount != expectedModCount)//expectedModCount是调用迭代器的时候当前的modCount 值
                throw new ConcurrentModificationException();
 }

原来在每次执行add的时候

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

这样并行的迭代和添加肯定会造成modCount 的修改,会抛出异常。


二、如何解决

CopyOnWriteArrayList的出现,这个类使用和list完全一样,而且他是放到java.util.concurrent包下,那么可以肯定他是线程安全的,但是他不像Vector一样,他是绝对的线程安全。

分析add方法

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

很简单,获取数组,然后根据当前数据拷贝一个新数组,容量+1,然后赋值,最后把引用替换。

这也就说说迭代的对象数组和正在增加的操作的数组其实不是一个新的引用,这样就保证了不会出现问题

但是还是要慎用,因为缺点也很明显

  • 花费代价很大
  • 读和写中间过程会出现不一致的现象,但是保持最终一致性

你可能感兴趣的:(java集合)