一、ArrayList
1、初步小理解
默认值是多少?
(1)查看源码可知默认值是10,但是没找到初始化为10的代码?
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** Constructs an empty list with an initial capacity of ten.*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
(2)查看新增方法,在calculateCapacity中可知默认的容量是10。
public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;}// 确定容量private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}// 判断是否需要扩容private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果最小需要空间比elementData的内存空间要大,则需要扩容if (minCapacity - elementData.length > 0)//扩容grow(minCapacity);}// 计算出容量,小于10,则返回10(可知默认的容量是10)private static final int DEFAULT_CAPACITY = 10;private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}// 进行扩容 1.5倍private void grow(int minCapacity) {// 获取到ArrayList中elementData数组的内存空间长度int oldCapacity = elementData.length;// 扩容至原来的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);// 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,// 不够就将数组长度设置为需要的长度if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//若预设值大于默认的最大值检查是否溢出if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间// 并将elementData的数据复制到新的内存空间elementData = Arrays.copyOf(elementData, newCapacity);}
为什么线程不安全
线程不安全示例
public class NoSafeDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i <= 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,9));System.out.println(list);}).start();}// 报错:java.util.ConcurrentModificationException 线程不安全常见的异常(并发修改异常)// 报错原因:并发争抢修改导致}}
报错位置如下,可知
- modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数
- expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。
A线程 add走到checkForComodification()的时候,B线程刚add改变了modCount的值,就会出现modCount != expectedModCount
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no such// =========关注这里int expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}
变成线程安全
1、使用方法Collections.synchronizedList()
public class SafeDemo2 {public static void main(String[] args) {List<String> list = Collections.synchronizedList(new ArrayList<>());for (int i = 0; i <= 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,9));System.out.println(list);}).start();}}}
这个方法会返回一个新的同步list,如下。
public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :new SynchronizedList<>(list));}
该方法的新建和查询有一个互斥锁(mutex),每次只能要么查询要么新建
public boolean add(E e) {synchronized (mutex) {return c.add(e);}}public E get(int index) {synchronized (mutex) {return list.get(index);}}
2、使用java.util.concurrent包中的CopyOnWriteArrayList,写时复制
public class SafeDemo3 {public static void main(String[] args) {List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i <= 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,9));System.out.println(list);}).start();}}}
参考源码add()
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();}}public E get(int index) {return get(getArray(), index);}
CopyOnWrite容器即写时复制容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器进行copy,复制出一个新容器 Object[] newElements,新容器容量比原容器大1,将新元素放入新容器的最后,最后将原容器的引用指向新容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写时不同的容器。写的时候加锁,保证线程安全。
注意和ArrayList增加元素的区别:ArrayList是先设置好一个 具备容量的数组,然后将元素放进去;而CopyOnWriteArrayList是直接在新的数组里添加元素,然后将老数组的引用指向新数组。
