随笔博文

Java---CopyOnWriteArrayList详解

2023-08-17 09:35:48 michael007js 47

CopyOnWriteArrayList是ArrayList的线程安全版本,从他的名字可以推测。CopyOnWriteArrayList是在有写操作的时候会copy一份数据,然后写完再设置成新的数据。CopyOnWriteArrayList适用于读多写少的并发场景。图片关键词

上面的图片展示你了CopyOnWriteArrayList的类图,可以看到它实现了List接口,如果去看ArrayList的类图的话,可以发现也是实现了List接口,也就得出一句废话,ArrayList提供的api,CopyOnWriteArrayList也提供,下文中来分析CopyOnWriteArrayList是如何来做到线程安全的实现读写数据的,而且也会顺便对比ArrayList的等效实现为什么不支持线程安全的。下面首先展示了CopyOnWriteArrayList中比较重要的成员:

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

可以看到,CopyOnWriteArrayList使用了ReentrantLock来支持并发操作,array就是实际存放数据的数组对象。ReentrantLock是一种支持重入的独占锁,任意时刻只允许一个线程获得锁,所以可以安全的并发去写数组,接下来看一下CopyOnWriteArrayList是如何使用这个lock来实现并发写的,下面首先展示了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(); } } 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

他的缺点:

内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。

数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】

CopyOnWriteArrayList读取时不加锁,只是写入、删除、修改时加锁,所以一个线程X读取的时候另一个线程Y可能执行remove操作。remove操作首先要获取独占锁,然后进行写时复制操作,就是复制一份当前的array数组,然后在复制的新数组里面删除线程X通过get访问的元素,比如:1。删除完成后让array指向这个新的数组。 在线程x执行get操作的时候并不是直接通过全局array访问数组元素而是通过方法的形参a访问的,a指向的地址和array指向的地址在调用get方法的那一刻是一样的,都指向了堆内存的数组对象。之后改变array指向的地址并不影响get的访问,因为在调用get方法的那一刻形参a指向的内存地址就已经确定了,不会改变。所以读的仍然是旧数组。

对Arrays.copyOf的认识

首先Arrays.copyOf(Object[] , length),相对于数组来说,是深拷贝,但是相对于数组元素来说,只有数组为一维数组,并且元素为基本类型、包装类、String类为深拷贝,其他都为浅拷贝(针对的是数组元素)。

代码实例:

public class Test{ staticclass Person{ int age ; String name;

  public Person(int age, String name) {
     this.age = age;
     this.name = name;
 }

  @Override
  public String toString() {
      return "Person{" +
              "age=" + age +
              ", name='" + name + '\'' +
              '}';
  }

} public static void main(String[] args) { Person[] people = new Person[2]; Person person1 = new Person(45,"dsad"); Person person2 = new Person(105,"zhuzhu"); people[0] = person1; people[1] = person2; Person[] people1 = Arrays.copyOf(people,people.length+1); System.out.println(Arrays.toString(people)); System.out.println(Arrays.toString(people1));

// 两者谁都可以改变,所以可以看出来,这个复制只是引用的复制,
//而真正的对象其实还是同一个。

 people[0].age = 456;

 System.out.println("---------");

 System.out.println(Arrays.toString(people));
 System.out.println(Arrays.toString(people1));
}

}

运行结果:


这里其实并没有把具体的对象复制,而是复制了对象的引用而已。如果我们使用的是int[] 类型的数组,那么就会改变了。

代码演示:

public class Test{ staticclass Person{ int age ; String name;

  public Person(int age, String name) {
     this.age = age;
     this.name = name;
 }

  @Override
  public String toString() {
      return "Person{" +
              "age=" + age +
              ", name='" + name + '\'' +
              '}';
  }

} public static void main(String[] args) { int[] arr1 = {4,5,6}; int[] ints = Arrays.copyOf(arr1, arr1.length); System.out.println(Arrays.toString(arr1)); System.out.println(Arrays.toString(ints)); System.out.println("--------------"); arr1[0] = 12; System.out.println(Arrays.toString(arr1)); System.out.println(Arrays.toString(ints)); } }

运行结果:


可以直观的看到,一个数组变换了,另一个没有变换。

下面这个相当于是一个set(index,val)源码方法的一个易懂的方式:

public class Test{ staticclass Person{ int age ; String name;

  public Person(int age, String name) {
     this.age = age;
     this.name = name;
 }

  @Override
  public String toString() {
      return "Person{" +
              "age=" + age +
              ", name='" + name + '\'' +
              '}';
  }

} public static void main(String[] args) { Person[] people = new Person[2]; Person person1 = new Person(45,"dsad"); Person person2 = new Person(105,"zhuzhu"); people[0] = person1; people[1] = person2; Person[] people1 = Arrays.copyOf(people,people.length+1);

  System.out.println(Arrays.toString(people));
 System.out.println(Arrays.toString(people));

 people1[0] = new Person(45777,"456");

 System.out.println(Arrays.toString(people));
 System.out.println(Arrays.toString(people1));
 people = people1;
 System.out.println(Arrays.toString(people));
 System.out.println(Arrays.toString(people1));
}

}

代码结果:

img



首页
关于博主
我的博客
搜索