1、Java在JDK1.5之前基本上对所有集合都实现了线程同步版本synchronized*,用集合工具类Collections即可得到,如下都为Collections中的方法:
- static <T> Collection<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是更好的选择。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步