创建使用流程
ArrayList
初始化之后,elementData是一个空数组,优点为节省内存空间;
当我们使用arrayList.add(1)添加第一个元素时
用来作为ArrayList存储容器的数组elementData会第一次扩容为10
之后扩容都是按照1.5倍的机制扩容,
扩容使用grow函数中的Arrays.copyOf(elementData,minCapacity)
- Arrays.copyOf(T[] original,int newLength ):拷贝数组,其内部调用了System.arrayCopy()方法,从下标0开始,如果超过原数组长度,会用null进行填充。
我们每次在使用add进行元素添加之前,都是使用成员变量size进行容量的判断,
如果size+1大于当前数组长度,此时就需要扩容,就会调用grow方法
注意:ArrayList
中的10表示的不是初值,而是起始容量initialCapacity;
线程不安全举例1
import java.util.ArrayList;import java.util.List;import java.util.UUID;/*** ClassName:ArrayListNotSafeDemo* Package:PACKAGE_NAME* Description:** @Date:2021/8/23 19:47* @Author:zhanglei@3417529439@qq.com*/public class ArrayListNotSafeDemo {public static void main( String[] args ) {List<String> list = new ArrayList<>();for (int i = 1; i < 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(1,8));System.out.println(list);}).start();}}}
上面代码在成功输出一些数据后会报java.util.ConcurrentModificationException异常
异常原因:

那么示例代码的迭代器是在哪里体现的?
- System.out.println(list)相当于System.out.println(list.toString());

下面是ArrayList从其父类或超类或继承来的toString方法 ```java public String toString() {
Iterator<E> it = iterator();if (! it.hasNext())return "[]";StringBuilder sb = new StringBuilder();sb.append('[');for (;;) {E e = it.next();sb.append(e == this ? "(this Collection)" : e);if (! it.hasNext())return sb.append(']').toString();sb.append(',').append(' ');}
}
即集合类的toString方法会生成迭代器但是我不觉得这个异常的出现跟add加不加锁有关系<br />add加了锁的情况下<br />a线程在遍历时,a线程已经释放了add方法的锁,此时b线程进行add,同样会报这个异常啊我觉得问题应该出在迭代器遍历处,而不是add处;<br />异常是迭代器发出来的,而不是add发出来的,add只会修改modCount的值,异常会依赖这个值<br />例子中的ArrayList换成Vector后不会报ConcurrentModificationException的原因:<br />Vector的迭代器中的next()方法也会加锁,Synchronized(Vector.this)<br />这里的Vector.this的意思:([https://www.zhihu.com/question/55565290/answer/145355951](https://www.zhihu.com/question/55565290/answer/145355951))<br />因为Itr这个类是Vector类的内部类,所以如果要想在内部类中使用外部类的实例对象,必须用外部类.this<br />所以说这个锁就是Vector的对象锁,联系到Vector的add方法也是对象锁<br />这就能解释为什么Vector不会报ConcurrentModificationException我上面的想法不对<br />我新的想法:<br />这里的iterator方法上有一个synchronized锁,对应的是对象锁<br />add方法上也有一个synchronized锁,对应的是对象锁<br />二者两边锁是同一把,因为这两个方法是同一个对象调用<br />我觉得这才能解释为什么Vector不会报ConcurrentModificationException```javapublic synchronized Iterator<E> iterator() {return new Itr();}
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {// Racy but within spec, since modifications are checked// within or after synchronization in next/previousreturn cursor != elementCount;}public E next() {synchronized (Vector.this) {checkForComodification();int i = cursor;if (i >= elementCount)throw new NoSuchElementException();cursor = i + 1;return elementData(lastRet = i);}}public void remove() {if (lastRet == -1)throw new IllegalStateException();synchronized (Vector.this) {checkForComodification();Vector.this.remove(lastRet);expectedModCount = modCount;}cursor = lastRet;lastRet = -1;}@Overridepublic void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);synchronized (Vector.this) {final int size = elementCount;int i = cursor;if (i >= size) {return;}@SuppressWarnings("unchecked")final E[] elementData = (E[]) Vector.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {action.accept(elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}
我们再来看下ArrayList的迭代器方法
长啥样
public Iterator<E> iterator() {return new Itr();}
由于ArrayList的iterator方法和add方法都没有任何锁
所以在一个线程迭代的过程中,另一个线程进行了add操作,就会报ConcurrentModificationException
我们再来看下CopyOnWriteArryaList的迭代器方法
可以发现尽管它的add方法用了lock锁,但是迭代器没有加锁,
因为它是读写分离的,读和写用的不是同一份数据,不会出现ConcurrentModificationException
public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);}
static final class COWIterator<E> implements ListIterator<E> {/** Snapshot of the array */private final Object[] snapshot;/** Index of element to be returned by subsequent call to next. */private int cursor;private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;snapshot = elements;}public boolean hasNext() {return cursor < snapshot.length;}public boolean hasPrevious() {return cursor > 0;}@SuppressWarnings("unchecked")public E next() {if (! hasNext())throw new NoSuchElementException();return (E) snapshot[cursor++];}@SuppressWarnings("unchecked")public E previous() {if (! hasPrevious())throw new NoSuchElementException();return (E) snapshot[--cursor];}public int nextIndex() {return cursor;}public int previousIndex() {return cursor-1;}/*** Not supported. Always throws UnsupportedOperationException.* @throws UnsupportedOperationException always; {@code remove}* is not supported by this iterator.*/public void remove() {throw new UnsupportedOperationException();}/*** Not supported. Always throws UnsupportedOperationException.* @throws UnsupportedOperationException always; {@code set}* is not supported by this iterator.*/public void set(E e) {throw new UnsupportedOperationException();}/*** Not supported. Always throws UnsupportedOperationException.* @throws UnsupportedOperationException always; {@code add}* is not supported by this iterator.*/public void add(E e) {throw new UnsupportedOperationException();}@Overridepublic void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);Object[] elements = snapshot;final int size = elements.length;for (int i = cursor; i < size; i++) {@SuppressWarnings("unchecked") E e = (E) elements[i];action.accept(e);}cursor = size;}}
线程不安全举例2
public class ArrayListNotSafeDemo {public static void main( String[] args ) throws InterruptedException {List<String> list = new ArrayList<>();for (int i = 0; i < 30; i++) {new Thread(()->{for (int j = 0; j < 1000 ; j++) {list.add(new Random().nextInt(100)+"");}}).start();}Thread.sleep(3000);System.out.println(list.size());}}
输出:
- 报java.lang.ArrayIndexOutOfBoundsException
说明还没正式扩容,就准备赋值了
就是判断大小和正式扩容之间的时间差
- 输出来的值小于30000
此时说明元素出现了覆盖
- 上面的就是add方法没有加锁带来的
Vector?No
将ArrayList换成Vector可以解决上面的异常
但是一般不适用Vector,因为其虽然可以保证数据一致性,但是并发性太差
Vector出来的比ArrayList都早,所以这ArrayList换成Vector肯定不是好的选择
加入synchronized的方法效率就是低,copyOnWriteArrayList的add方法用的是lock锁
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;}
public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);}
Vector中的读和写都加了锁,而且是同一把锁
没有做到读写分离
Colletions工具类
Collections.synchronizedList(new ArrayList<>())
CopyOnWriteArrayList(写时复制)
new CopyOnWriteArrayList<>()
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);}
CopyOnWriteArrayList的读和写时分离的
因为写的时候是有锁的,读的时候是没有锁的
即多个线程写的时候任意时刻只能有一个线程在写
读的时候可以多个线程在同时读

看样子每次add元素都要进行扩容
为什么要使用可重入锁
我觉得先要理解读写锁在来看这个


https://www.jianshu.com/p/dcd5fc3f1568
上面有的地方讲错了,比如CopyOnWriteArrayList用的是lock锁而不是synchronized
面试题

Collections工具类的使用:
将当前的ArrayList封装成一个Collections工具类的内部类SynchronizedList
它的get和add方法如下所示:
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
两个都加了锁,所以效率低
