君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理
  1057 随笔 :: 381 文章 :: 141 评论 :: 169万 阅读

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

Java代码  收藏代码
  1. static <T> Collection<T>   
  2.  synchronizedCollection(Collection<T> c)   
  3.           返回指定 collection 支持的同步(线程安全的)collection。   
  4. static <T> List<T>   
  5.  synchronizedList(List<T> list)   
  6.           返回指定列表支持的同步(线程安全的)列表。   
  7. static <K,V> Map<K,V>   
  8.  synchronizedMap(Map<K,V> m)   
  9.           返回由指定映射支持的同步(线程安全的)映射。   
  10. static <T> Set<T>   
  11.  synchronizedSet(Set<T> s)   
  12.           返回指定 set 支持的同步(线程安全的)set。   
  13. static <K,V> SortedMap<K,V>   
  14.  synchronizedSortedMap(SortedMap<K,V> m)   
  15.           返回指定有序映射支持的同步(线程安全的)有序映射。   
  16. static <T> SortedSet<T>   
  17.  synchronizedSortedSet(SortedSet<T> s)   
  18.           返回指定有序 set 支持的同步(线程安全的)有序 set。   

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

Java代码  收藏代码
  1. private static class SynchronizedMap<K,V>  
  2.     implements Map<K,V>, Serializable {  
  3.         private final Map<K,V> m;     // Backing Map  
  4.         final Object      mutex;  
  5.     SynchronizedMap(Map<K,V> m) {  
  6.             if (m==null)  
  7.                 throw new NullPointerException();  
  8.             this.m = m;  
  9.             mutex = this;  
  10.         }  
  11.     public V get(Object key) {  
  12.         synchronized(mutex) {return m.get(key);}  
  13.         }  
  14.   
  15.     public V put(K key, V value) {  
  16.         synchronized(mutex) {return m.put(key, value);}  
  17.         }  
  18.         ......  
  19. }  

 

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

Java代码  收藏代码
  1. Collection<String> users = new ArrayList<String>();  
  2. users.add("first");  
  3. users.add("second");  
  4. users.add("third");  
  5. Iterator<String> itrUsers = users.iterator();  
  6. while (itrUsers.hasNext()) {  
  7.     String user = itrUsers.next();  
  8.     if ("first".equals(user)) {  
  9.         users.remove(user);  
  10.     }  
  11.     System.out.println(user);  
  12. }  

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

Java代码  收藏代码
  1. public E next() {  
  2.            checkForComodification();  
  3.     try {  
  4.     E next = get(cursor);  
  5.     lastRet = cursor++;  
  6.     return next;  
  7.     } catch (IndexOutOfBoundsException e) {  
  8.     checkForComodification();  
  9.     throw new NoSuchElementException();  
  10.     }  
  11. }  
  12. final void checkForComodification() {  
  13.     if (modCount != expectedModCount)  
  14.     throw new ConcurrentModificationException();  
  15. }  
  16.    }  

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

Java代码  收藏代码
  1. Collection<String> users = new CopyOnWriteArrayList<String>();  

 

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

Java代码  收藏代码
  1. public E get(int index) {  
  2.     return (E)(getArray()[index]);  
  3. }  

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

构造方法:

Java代码  收藏代码
  1. public CopyOnWriteArrayList() {  
  2.     setArray(new Object[0]);  
  3. }  

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

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

Java代码  收藏代码
  1.    public boolean add(E e) {  
  2. final ReentrantLock lock = this.lock;  
  3. lock.lock();  
  4. try {  
  5.     Object[] elements = getArray();  
  6.     int len = elements.length;  
  7.     Object[] newElements = Arrays.copyOf(elements, len + 1);  
  8.     newElements[len] = e;  
  9.     setArray(newElements);  
  10.     return true;  
  11. finally {  
  12.     lock.unlock();  
  13. }  
  14.    }  

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

Java代码  收藏代码
  1. final void setArray(Object[] a) {  
  2.     array = a;  
  3. }  

 

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

 

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

Java代码  收藏代码
  1. public Iterator<E> iterator() {  
  2.     return new COWIterator<E>(getArray(), 0);  
  3. }  

 

 4、和ArrayList性能比较

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

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

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

posted on   刺猬的温驯  阅读(233)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示