如何解决ArrayList线程不安全

  • 方法1:使用Vector
  • 方法2:使用Collections工具类中的Collections.synchronizedList(new ArrayList) 方法
  • 方法3:JUC下的CopyOnWriteArrayList类,写时复制

    1. 类图

截屏2021-07-09 下午4.36.48.png
写时复制、读写分离

  1. public class CopyOnWriteArrayList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

2. 成员变量

  1. /** The lock protecting all mutators */
  2. // 可重入锁,执行数组的变换操作时加锁,get操作不用加锁(读共享、写独享)
  3. final transient ReentrantLock lock = new ReentrantLock();
  4. /** The array, accessed only via getArray/setArray. */
  5. // 可见性、有序性,存放元素的数组
  6. private transient volatile Object[] array;
  7. // get、set方法
  8. final Object[] getArray() {
  9. return array;
  10. }
  11. /**
  12. * Sets the array.
  13. */
  14. final void setArray(Object[] a) {
  15. array = a;
  16. }

3.构造方法

  1. public CopyOnWriteArrayList() {
  2. setArray(new Object[0]);
  3. }
  4. /**
  5. * Creates a list containing the elements of the specified
  6. * collection, in the order they are returned by the collection's
  7. * iterator.
  8. *
  9. * @param c the collection of initially held elements
  10. * @throws NullPointerException if the specified collection is null
  11. */
  12. public CopyOnWriteArrayList(Collection<? extends E> c) {
  13. Object[] elements;
  14. if (c.getClass() == CopyOnWriteArrayList.class)
  15. elements = ((CopyOnWriteArrayList<?>)c).getArray();
  16. else {
  17. elements = c.toArray();
  18. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  19. if (elements.getClass() != Object[].class)
  20. elements = Arrays.copyOf(elements, elements.length, Object[].class);
  21. }
  22. setArray(elements);
  23. }
  24. /**
  25. * Creates a list holding a copy of the given array.
  26. *
  27. * @param toCopyIn the array (a copy of this array is used as the
  28. * internal array)
  29. * @throws NullPointerException if the specified array is null
  30. */
  31. public CopyOnWriteArrayList(E[] toCopyIn) {
  32. setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
  33. }

4.方法

4.1 add方法

  • _public boolean _add(E e):末尾添加元素
  • _public void _add(_int _index, E element) :指定位置添加元素
  • _public boolean _addIfAbsent(E e):如果不存在加入元素
  • _public boolean _addAll(Collection<? _extends _E> c)
  • _public boolean _addAll(_int _index, Collection<? _extends _E> c)

    _public boolean _add(E e)

  1. public boolean add(E e) {
  2. final ReentrantLock lock = this.lock;
  3. // 获取独占锁
  4. lock.lock();
  5. try {
  6. // 获取数组内容
  7. Object[] elements = getArray();
  8. // 长度
  9. int len = elements.length;
  10. // 复制一份新的,长度为原长度+1
  11. Object[] newElements = Arrays.copyOf(elements, len + 1);
  12. // 放入结尾新增的元素e
  13. newElements[len] = e;
  14. // 重新赋值原数组
  15. setArray(newElements);
  16. return true;
  17. } finally {
  18. // 释放独占锁
  19. lock.unlock();
  20. }
  21. }
  • CopyOnWriteArrayList是如何保证【写】时线程安全的?因为用了ReentrantLock独占锁,保证同时只有一个线程对集合进行修改操作。
  • 数据是存储在CopyOnWriteArrayList中的array数组中的。
  • 在添加元素的时候,并不是直接往array里面add元素,而是复制出来了一个新的数组,并且复制出来的数组的长度是 【旧数组的长度+1】,再把旧的数组替换成新的数组,这是尤其需要注意的。

    _public void _add(_int _index, E element)

  1. public void add(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. // index合法性检查
  8. if (index > len || index < 0)
  9. throw new IndexOutOfBoundsException("Index: "+index+
  10. ", Size: "+len);
  11. // 以下添加元素的思想同ArrayList
  12. Object[] newElements;
  13. // 获取移动的元素个数
  14. int numMoved = len - index;
  15. if (numMoved == 0)
  16. // 复制新的,在末尾添加就可以了
  17. newElements = Arrays.copyOf(elements, len + 1);
  18. else {
  19. // 在index前后分段复制
  20. newElements = new Object[len + 1];
  21. System.arraycopy(elements, 0, newElements, 0, index);
  22. System.arraycopy(elements, index, newElements, index + 1,
  23. numMoved);
  24. }
  25. newElements[index] = element;
  26. // 重新赋值原数组
  27. setArray(newElements);
  28. } finally {
  29. lock.unlock();
  30. }
  31. }

_public boolean _addIfAbsent(E e)

  1. public boolean addIfAbsent(E e) {
  2. Object[] snapshot = getArray();
  3. // 先判断当前快照的数组有没有这个元素,没有就进入addIfAbsent方法添加
  4. return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
  5. addIfAbsent(e, snapshot);
  6. }
  7. /**
  8. * A version of addIfAbsent using the strong hint that given
  9. * recent snapshot does not contain e.
  10. */
  11. // 快照加
  12. private boolean addIfAbsent(E e, Object[] snapshot) {
  13. final ReentrantLock lock = this.lock;
  14. lock.lock();
  15. try {
  16. Object[] current = getArray();
  17. int len = current.length;
  18. // 如果快照和当前最新的数组地址不一致,说明被别的线程动了
  19. if (snapshot != current) {
  20. // Optimize for lost race to another addXXX operation
  21. // 计算较小的长度
  22. int common = Math.min(snapshot.length, len);
  23. // 遍历,
  24. for (int i = 0; i < common; i++)
  25. // 如果当前数组和快照数组中i对应的位置的值不相等,且
  26. if (current[i] != snapshot[i] && eq(e, current[i]))
  27. return false;
  28. if (indexOf(e, current, common, len) >= 0)
  29. return false;
  30. }
  31. // 否则,没有别人动,可以添加
  32. Object[] newElements = Arrays.copyOf(current, len + 1);
  33. newElements[len] = e;
  34. setArray(newElements);
  35. return true;
  36. } finally {
  37. lock.unlock();
  38. }
  39. }

存快照

4.2 查找方法

  • _public boolean _contains(Object o)
  • _public _E get(_int _index) :共享的不加锁。存在弱一致性问题,别人修改的时候该线程不知道
  1. public E get(int index) {
  2. return get(getArray(), index);
  3. }
  4. private E get(Object[] a, int index) {
  5. return (E) a[index];
  6. }
  1. // 取当前的array查询
  2. public boolean contains(Object o) {
  3. Object[] elements = getArray();
  4. return indexOf(o, elements, 0, elements.length) >= 0;
  5. }
  6. private static int indexOf(Object o, Object[] elements,
  7. int index, int fence) {
  8. if (o == null) {
  9. for (int i = index; i < fence; i++)
  10. if (elements[i] == null)
  11. return i;
  12. } else {
  13. for (int i = index; i < fence; i++)
  14. if (o.equals(elements[i]))
  15. return i;
  16. }
  17. return -1;
  18. }

4.3 修改方法

  1. public E set(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. // 拿到当前的array数组
  6. Object[] elements = getArray();
  7. E oldValue = get(elements, index);
  8. 如果oldValue和新值不相等
  9. if (oldValue != element) {
  10. int len = elements.length;
  11. // 先复制一份,在新数组中修改,在重新赋值回去
  12. Object[] newElements = Arrays.copyOf(elements, len);
  13. newElements[index] = element;
  14. setArray(newElements);
  15. } else {
  16. // 为了保证volatile 语义,即使没有修改,也要替换成新的数组
  17. // Not quite a no-op; ensures volatile write semantics
  18. setArray(elements);
  19. }
  20. return oldValue;
  21. } finally {
  22. lock.unlock();
  23. }
  24. }

4.4 删除方法

根据值删除

  1. // 如果存在,删除第一个出现的o元素;否则不变
  2. public boolean remove(Object o) {
  3. Object[] snapshot = getArray();
  4. int index = indexOf(o, snapshot, 0, snapshot.length);
  5. return (index < 0) ? false : remove(o, snapshot, index);
  6. }
  7. // 找它所在的位置,返回坐标
  8. private static int indexOf(Object o, Object[] elements,
  9. int index, int fence) {
  10. if (o == null) {
  11. for (int i = index; i < fence; i++)
  12. if (elements[i] == null)
  13. return i;
  14. } else {
  15. for (int i = index; i < fence; i++)
  16. if (o.equals(elements[i]))
  17. return i;
  18. }
  19. return -1;
  20. }
  21. // 快照删
  22. private boolean remove(Object o, Object[] snapshot, int index) {
  23. final ReentrantLock lock = this.lock;
  24. lock.lock();
  25. try {
  26. Object[] current = getArray();
  27. int len = current.length;
  28. if (snapshot != current) findIndex: {
  29. int prefix = Math.min(index, len);
  30. for (int i = 0; i < prefix; i++) {
  31. if (current[i] != snapshot[i] && eq(o, current[i])) {
  32. index = i;
  33. break findIndex;
  34. }
  35. }
  36. if (index >= len)
  37. return false;
  38. if (current[index] == o)
  39. break findIndex;
  40. index = indexOf(o, current, index, len);
  41. if (index < 0)
  42. return false;
  43. }
  44. Object[] newElements = new Object[len - 1];
  45. System.arraycopy(current, 0, newElements, 0, index);
  46. System.arraycopy(current, index + 1,
  47. newElements, index,
  48. len - index - 1);
  49. setArray(newElements);
  50. return true;
  51. } finally {
  52. lock.unlock();
  53. }
  54. }

根据下标删除

  1. public E remove(int index) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. // 获取该位置的元素oldValue
  8. E oldValue = get(elements, index);
  9. // 计算移动的个数
  10. int numMoved = len - index - 1;
  11. // 如果移动个数为0
  12. if (numMoved == 0)
  13. // 为了保证volatile 语义,即使没有修改,也要替换成新的数组
  14. setArray(Arrays.copyOf(elements, len - 1));
  15. else {
  16. // 否则,需要删除,同样new一个新数组进行操作,最后再重新赋值
  17. Object[] newElements = new Object[len - 1];
  18. // 在index前后分两段复制
  19. System.arraycopy(elements, 0, newElements, 0, index);
  20. System.arraycopy(elements, index + 1, newElements, index,
  21. numMoved);
  22. setArray(newElements);
  23. }
  24. return oldValue;
  25. } finally {
  26. lock.unlock();
  27. }
  28. }