创建使用流程

ArrayList arrayList=new 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 arrayList=new ArrayList(10);
中的10表示的不是初值,而是起始容量initialCapacity;

线程不安全举例1

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.UUID;
  4. /**
  5. * ClassName:ArrayListNotSafeDemo
  6. * Package:PACKAGE_NAME
  7. * Description:
  8. *
  9. * @Date:2021/8/23 19:47
  10. * @Author:zhanglei@3417529439@qq.com
  11. */
  12. public class ArrayListNotSafeDemo {
  13. public static void main( String[] args ) {
  14. List<String> list = new ArrayList<>();
  15. for (int i = 1; i < 30; i++) {
  16. new Thread(()->{
  17. list.add(UUID.randomUUID().toString().substring(1,8));
  18. System.out.println(list);
  19. }).start();
  20. }
  21. }
  22. }

上面代码在成功输出一些数据后会报java.util.ConcurrentModificationException异常
异常原因:
image.png
image.png
那么示例代码的迭代器是在哪里体现的?

  1. System.out.println(list)相当于System.out.println(list.toString());

image.png

  1. 下面是ArrayList从其父类或超类或继承来的toString方法 ```java public String toString() {

    1. Iterator<E> it = iterator();
    2. if (! it.hasNext())
    3. return "[]";
    4. StringBuilder sb = new StringBuilder();
    5. sb.append('[');
    6. for (;;) {
    7. E e = it.next();
    8. sb.append(e == this ? "(this Collection)" : e);
    9. if (! it.hasNext())
    10. return sb.append(']').toString();
    11. sb.append(',').append(' ');
    12. }

    }

  1. 即集合类的toString方法会生成迭代器
  2. 但是我不觉得这个异常的出现跟add加不加锁有关系<br />add加了锁的情况下<br />a线程在遍历时,a线程已经释放了add方法的锁,此时b线程进行add,同样会报这个异常啊
  3. 我觉得问题应该出在迭代器遍历处,而不是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
  4. 我上面的想法不对<br />我新的想法:<br />这里的iterator方法上有一个synchronized锁,对应的是对象锁<br />add方法上也有一个synchronized锁,对应的是对象锁<br />二者两边锁是同一把,因为这两个方法是同一个对象调用<br />我觉得这才能解释为什么Vector不会报ConcurrentModificationException
  5. ```java
  6. public synchronized Iterator<E> iterator() {
  7. return new Itr();
  8. }
  1. private class Itr implements Iterator<E> {
  2. int cursor; // index of next element to return
  3. int lastRet = -1; // index of last element returned; -1 if no such
  4. int expectedModCount = modCount;
  5. public boolean hasNext() {
  6. // Racy but within spec, since modifications are checked
  7. // within or after synchronization in next/previous
  8. return cursor != elementCount;
  9. }
  10. public E next() {
  11. synchronized (Vector.this) {
  12. checkForComodification();
  13. int i = cursor;
  14. if (i >= elementCount)
  15. throw new NoSuchElementException();
  16. cursor = i + 1;
  17. return elementData(lastRet = i);
  18. }
  19. }
  20. public void remove() {
  21. if (lastRet == -1)
  22. throw new IllegalStateException();
  23. synchronized (Vector.this) {
  24. checkForComodification();
  25. Vector.this.remove(lastRet);
  26. expectedModCount = modCount;
  27. }
  28. cursor = lastRet;
  29. lastRet = -1;
  30. }
  31. @Override
  32. public void forEachRemaining(Consumer<? super E> action) {
  33. Objects.requireNonNull(action);
  34. synchronized (Vector.this) {
  35. final int size = elementCount;
  36. int i = cursor;
  37. if (i >= size) {
  38. return;
  39. }
  40. @SuppressWarnings("unchecked")
  41. final E[] elementData = (E[]) Vector.this.elementData;
  42. if (i >= elementData.length) {
  43. throw new ConcurrentModificationException();
  44. }
  45. while (i != size && modCount == expectedModCount) {
  46. action.accept(elementData[i++]);
  47. }
  48. // update once at end of iteration to reduce heap write traffic
  49. cursor = i;
  50. lastRet = i - 1;
  51. checkForComodification();
  52. }
  53. }
  54. final void checkForComodification() {
  55. if (modCount != expectedModCount)
  56. throw new ConcurrentModificationException();
  57. }
  58. }

我们再来看下ArrayList的迭代器方法
长啥样

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

由于ArrayList的iterator方法和add方法都没有任何锁
所以在一个线程迭代的过程中,另一个线程进行了add操作,就会报ConcurrentModificationException

我们再来看下CopyOnWriteArryaList的迭代器方法
可以发现尽管它的add方法用了lock锁,但是迭代器没有加锁,
因为它是读写分离的,读和写用的不是同一份数据,不会出现ConcurrentModificationException

  1. public Iterator<E> iterator() {
  2. return new COWIterator<E>(getArray(), 0);
  3. }
  1. static final class COWIterator<E> implements ListIterator<E> {
  2. /** Snapshot of the array */
  3. private final Object[] snapshot;
  4. /** Index of element to be returned by subsequent call to next. */
  5. private int cursor;
  6. private COWIterator(Object[] elements, int initialCursor) {
  7. cursor = initialCursor;
  8. snapshot = elements;
  9. }
  10. public boolean hasNext() {
  11. return cursor < snapshot.length;
  12. }
  13. public boolean hasPrevious() {
  14. return cursor > 0;
  15. }
  16. @SuppressWarnings("unchecked")
  17. public E next() {
  18. if (! hasNext())
  19. throw new NoSuchElementException();
  20. return (E) snapshot[cursor++];
  21. }
  22. @SuppressWarnings("unchecked")
  23. public E previous() {
  24. if (! hasPrevious())
  25. throw new NoSuchElementException();
  26. return (E) snapshot[--cursor];
  27. }
  28. public int nextIndex() {
  29. return cursor;
  30. }
  31. public int previousIndex() {
  32. return cursor-1;
  33. }
  34. /**
  35. * Not supported. Always throws UnsupportedOperationException.
  36. * @throws UnsupportedOperationException always; {@code remove}
  37. * is not supported by this iterator.
  38. */
  39. public void remove() {
  40. throw new UnsupportedOperationException();
  41. }
  42. /**
  43. * Not supported. Always throws UnsupportedOperationException.
  44. * @throws UnsupportedOperationException always; {@code set}
  45. * is not supported by this iterator.
  46. */
  47. public void set(E e) {
  48. throw new UnsupportedOperationException();
  49. }
  50. /**
  51. * Not supported. Always throws UnsupportedOperationException.
  52. * @throws UnsupportedOperationException always; {@code add}
  53. * is not supported by this iterator.
  54. */
  55. public void add(E e) {
  56. throw new UnsupportedOperationException();
  57. }
  58. @Override
  59. public void forEachRemaining(Consumer<? super E> action) {
  60. Objects.requireNonNull(action);
  61. Object[] elements = snapshot;
  62. final int size = elements.length;
  63. for (int i = cursor; i < size; i++) {
  64. @SuppressWarnings("unchecked") E e = (E) elements[i];
  65. action.accept(e);
  66. }
  67. cursor = size;
  68. }
  69. }

线程不安全举例2

  1. public class ArrayListNotSafeDemo {
  2. public static void main( String[] args ) throws InterruptedException {
  3. List<String> list = new ArrayList<>();
  4. for (int i = 0; i < 30; i++) {
  5. new Thread(()->{
  6. for (int j = 0; j < 1000 ; j++) {
  7. list.add(new Random().nextInt(100)+"");
  8. }
  9. }).start();
  10. }
  11. Thread.sleep(3000);
  12. System.out.println(list.size());
  13. }
  14. }

输出:

  1. 报java.lang.ArrayIndexOutOfBoundsException

说明还没正式扩容,就准备赋值了
就是判断大小和正式扩容之间的时间差

  1. 输出来的值小于30000

此时说明元素出现了覆盖

  1. 上面的就是add方法没有加锁带来的

Vector?No

将ArrayList换成Vector可以解决上面的异常
但是一般不适用Vector,因为其虽然可以保证数据一致性,但是并发性太差
Vector出来的比ArrayList都早,所以这ArrayList换成Vector肯定不是好的选择
加入synchronized的方法效率就是低,copyOnWriteArrayList的add方法用的是lock锁

  1. public synchronized boolean add(E e) {
  2. modCount++;
  3. ensureCapacityHelper(elementCount + 1);
  4. elementData[elementCount++] = e;
  5. return true;
  6. }
  1. public synchronized E get(int index) {
  2. if (index >= elementCount)
  3. throw new ArrayIndexOutOfBoundsException(index);
  4. return elementData(index);
  5. }

Vector中的读和写都加了锁,而且是同一把锁
没有做到读写分离

Colletions工具类

Collections.synchronizedList(new ArrayList<>())

CopyOnWriteArrayList(写时复制)

new CopyOnWriteArrayList<>()

  1. public boolean add(E e) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. Object[] newElements = Arrays.copyOf(elements, len + 1);
  8. newElements[len] = e;
  9. setArray(newElements);
  10. return true;
  11. } finally {
  12. lock.unlock();
  13. }
  14. }
  1. public E get(int index) {
  2. return get(getArray(), index);
  3. }

CopyOnWriteArrayList的读和写时分离的
因为写的时候是有锁的,读的时候是没有锁的
即多个线程写的时候任意时刻只能有一个线程在写
读的时候可以多个线程在同时读

image.png

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

image.png
image.png
https://www.jianshu.com/p/dcd5fc3f1568

上面有的地方讲错了,比如CopyOnWriteArrayList用的是lock锁而不是synchronized

面试题

image.png

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);}
}
两个都加了锁,所以效率低