JDK1.8 CopyOnWriteArrayList源码学习

ArrayList

底层:Object数组,非线程安全

默认容量:10,其实是0,第一次add时,才会主动去扩容

每一扩容,变为原来容量的1.5倍。10->15->22

/*      */   private void grow(int minCapacity)
/*      */   {
/*  254 */     int oldCapacity = elementData.length;
/*  255 */     int newCapacity = oldCapacity + (oldCapacity >> 1);
/*  256 */     if (newCapacity - minCapacity < 0)
/*  257 */       newCapacity = minCapacity;
/*  258 */     if (newCapacity - 2147483639 > 0) {
/*  259 */       newCapacity = hugeCapacity(minCapacity);
/*      */     }
/*  261 */     elementData = Arrays.copyOf(elementData, newCapacity);
/*      */   }

  

线程不安全

非线程安全的case:ConcurrentModificationException

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 30; i++) {
            Thread thread = new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            });
            thread.start();
        }
    }

  为什么会报错:遍历ArrayList时,另一线程执行add操作,会造成modCount变化,fail-fast思想

 */     final void checkForComodification() {
/*  900 */       if (modCount != expectedModCount) {
/*  901 */         throw new ConcurrentModificationException();
/*      */       }
/*      */     }

  

Exception in thread "Thread-25" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.hashmapt.ArrayListTest.lambda$0(ArrayListTest.java:29)
at java.lang.Thread.run(Thread.java:745)
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f, c4894981-260b-4b7b-8076-168703f7e8ba]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f, c4894981-260b-4b7b-8076-168703f7e8ba, c201001b-41a8-4919-ad5f-5e36bd86a56b]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f, c4894981-260b-4b7b-8076-168703f7e8ba, c201001b-41a8-4919-ad5f-5e36bd86a56b, a492afac-264b-4111-9876-3e0e01b606db, 48dcca36-b425-451e-ab8b-1ad0967bfa91]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f, c4894981-260b-4b7b-8076-168703f7e8ba, c201001b-41a8-4919-ad5f-5e36bd86a56b, a492afac-264b-4111-9876-3e0e01b606db, 48dcca36-b425-451e-ab8b-1ad0967bfa91, 65c3cb2c-b4d4-4b59-ad2a-0c2194733d58]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 96f5bb81-c50b-441b-9833-e81c364b3c5f, c4894981-260b-4b7b-8076-168703f7e8ba, c201001b-41a8-4919-ad5f-5e36bd86a56b, a492afac-264b-4111-9876-3e0e01b606db, 48dcca36-b425-451e-ab8b-1ad0967bfa91, 65c3cb2c-b4d4-4b59-ad2a-0c2194733d58, 80dca1e1-c3bb-4b70-9cbd-f3ebfb699093]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7]
[82b9f5c7-7c17-48a1-87e0-e1ff90e084bb, fd427bbf-6adf-45af-b806-5af9a044d6d7, 9

如何解决ArrayList线程不安全:

方法1:使用Vector

    public static void main(String[] args) {
//        List<String> list = new ArrayList<String>();
        List<String> list = new Vector<String>();
        for (int i = 0; i < 30; i++) {
            Thread thread = new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            });
            thread.start();
        }
    }

  

Vector是线程安全的容器,原理是对add,remove,iterator等所有方法都加synchronized锁

/*      */   public synchronized boolean add(E e)
/*      */   {
/*  781 */     modCount += 1;
/*  782 */     ensureCapacityHelper(elementCount + 1);
/*  783 */     elementData[(elementCount++)] = e;
/*  784 */     return true;
/*      */   }

  此容器虽然线程安全,但是效率极低,使用场景很少

方法2:使用Collections.synchronizedList

    public static void main(String[] args) {
//        List<String> list = new ArrayList<String>();
//        List<String> list = new Vector<String>();
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 30; i++) {
            Thread thread = new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            });
            thread.start();
        }
    }

  其原理同Vector,对所有操作加synchronized锁

方法三:使用CopyOnWriteArrayList

    public static void main(String[] args) {
//        List<String> list = new ArrayList<String>();
//        List<String> list = new Vector<String>();
//        List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 30; i++) {
            Thread thread = new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            });
            thread.start();
        }
    }

  原理:写时复制,即只对add操作加锁,读操作不加锁。

add时,拷贝一份副本到内存,扩容后,将array指针指向新的数组

/*      */   public boolean add(E e)
/*      */   {
/*  434 */     ReentrantLock lock = this.lock;
/*  435 */     lock.lock();
/*      */     try {
/*  437 */       Object[] elements = getArray();
/*  438 */       int len = elements.length;
/*  439 */       Object[] newElements = Arrays.copyOf(elements, len + 1);
/*  440 */       newElements[len] = e;
/*  441 */       setArray(newElements);
/*  442 */       return true;
/*      */     } finally {
/*  444 */       lock.unlock();
/*      */     }
/*      */   }

  此博客详细介绍了CopyOnWrite的使用场景及优缺点

聊聊并发-Java中的Copy-On-Write容器

程序员必备基础知识,CopyOnWrite思想,其实非常简单,源码解析

原文地址:https://www.cnblogs.com/lt123/p/13289146.html