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的使用场景及优缺点