Java 并发集合CopyOnWriteArrayList

1、Java在JDK1.5之前基本上对所有集合都实现了线程同步版本synchronized*,用集合工具类Collections即可得到,如下都为Collections中的方法:

static <T> Collections<T> 
 synchronizedCollection(Collection<T> c) 
          返回指定 collection 支持的同步(线程安全的)collection。 
static <T> List<T> 
 synchronizedList(List<T> list) 
          返回指定列表支持的同步(线程安全的)列表。 
static <K,V> Map<K,V> 
 synchronizedMap(Map<K,V> m) 
          返回由指定映射支持的同步(线程安全的)映射。 
static <T> Set<T> 
 synchronizedSet(Set<T> s) 
          返回指定 set 支持的同步(线程安全的)set。 
static <K,V> SortedMap<K,V> 
 synchronizedSortedMap(SortedMap<K,V> m) 
          返回指定有序映射支持的同步(线程安全的)有序映射。 
static <T> SortedSet<T> 
 synchronizedSortedSet(SortedSet<T> s) 
          返回指定有序 set 支持的同步(线程安全的)有序 set。 

其内部实现是在内部维护一个对应的集合类型,然后相应所有的操作都加上同步关键字synchronized,同步方法内再调用内部集合的方法,如下为synchronizedMap的实现:

private static class SynchronizedMap<K,V>
	implements Map<K,V>, Serializable {
        private final Map<K,V> m;     // Backing Map
        final Object      mutex;
	SynchronizedMap(Map<K,V> m) {
            if (m==null)
                throw new NullPointerException();
            this.m = m;
            mutex = this;
        }
	public V get(Object key) {
	    synchronized(mutex) {return m.get(key);}
        }

	public V put(K key, V value) {
	    synchronized(mutex) {return m.put(key, value);}
        }
        ......
}

 

 2、传统方式下的Collection在迭代集合时,不允许对集合进行修改,如下代码会抛出ConcurrentModificationException异常:

Collection<String> users = new ArrayList<String>();
users.add("first");
users.add("second");
users.add("third");
Iterator<String> itrUsers = users.iterator();
while (itrUsers.hasNext()) {
	String user = itrUsers.next();
	if ("first".equals(user)) {
		users.remove(user);
	}
	System.out.println(user);
}

 在调用next()方法时,由于删除了元素导致期望长度和实现长度不一至而抛出异常:

	public E next() {
            checkForComodification();
	    try {
		E next = get(cursor);
		lastRet = cursor++;
		return next;
	    } catch (IndexOutOfBoundsException e) {
		checkForComodification();
		throw new NoSuchElementException();
	    }
	}
	final void checkForComodification() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	}
    }

 把上面ArrayList换成CopyOnWriteArrayList即可完成在迭代中对集合进行操作,并且在多线程中也是安全的:

Collection<String> users = new CopyOnWriteArrayList<String>();

 

 3、CopyOnWriteArrayList是在Java JDK1.5中引入的并发类,在java.util.concurrent包下,它是ArrayList 的一个线程安全的变体,并且是在读时无锁的ArrayList:

    public E get(int index) {
        return (E)(getArray()[index]);
    }

 此方法很简单没有加锁,有可能会出现脏读的情况,但是性能非常高,对于写少读多且对脏数据要求不严的场景可以使用。

构造方法:

    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

 与ArrayList不同,CopyOnWriteArrayList创建一个大小为0的数组。

add(E)方法:add方法并没有使用内置锁,而是使用JDK1.5提供的显示锁ReentrantLock来保证线程的安全的:

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

 此处和ArrayList不同的是每次都创建一个新的Object数组,此数据的大小为当前数据大小加1,将之前的数组中的内容复制到新的数组中,并将新增加的对象放入到数组的末尾,最后把新数组的引用赋值给全局的数组对象:

    final void setArray(Object[] a) {
        array = a;
    }

 

 remove(Object)方法:和add方法一样,删除元素方法也采用JDK1.5的加锁方式来保证线程安全,但它和ArrayList采用的删除方法不同,它是创建一个长度比当前数组小1的新数组,然后遍历老数组,其它元素全部加到新数组,最后把新数组的引用赋值给全局的数组对象,它并没有使用System的arrayCopy来实现,可能会导致性能一定的下降。

 

iterator()迭代:调用iterator方法后创建一个新的COWIterator实例,并保存了一个当前数组的快照,在调用next遍历时仅对快照进行遍历,所以在迭代CopyOnWriteArrayList中操作其中元素不会像ArrayList一样抛出ConcurrentModificationException异常:

    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

 

 4、和ArrayList性能比较

单线程:在元素较少的情况下,两个类的性能基本上一至,但是到元素很多时,CopyOnWriteArrayList增加元素的删除元素性能会差一点

多线程:随着元素数量和线程数量的增加,CopyOnWriteArrayList在增加和删除元素的性能就会下降,而且比ArrayList性能低。但在查找元素时随着元素数量和线程数量的增加性能比ArrayList好。

在读多写少的并发场景中,CopyOnWriteArrayList比ArrayList是更好的选择。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值