Java CopyOnWriteArrayList与示例

时间:2020-01-09 10:35:09  来源:igfitidea点击:

Java中的CopyOnWriteArrayList与其他众所周知的对应ArrayList一样,实现了List接口,并且是java.util.concurrent包的一部分。 CopyOnWriteArrayList与ArrayList的不同之处在于它是ArrayList的线程安全变体。

有关Java中的CopyOnWriteArrayList和ArrayList之间的更多区别,请参阅此文章Java中的ArrayList和CopyOnWriteArrayList之间的区别

Java CopyOnWriteArrayList如何线程安全

Java中的CopyOnWriteArrayList与ArrayList一样,使用Object类型的数组存储其元素。为了线程安全,如其名称所示,CopyOnWriteArrayList的实现为任何修改操作(如添加,设置,替换等)创建基础数组的新副本。

当遍历操作比突变更多时,这使得CopyOnWriteArrayList是一个不错的选择,因为可以在不进行任何推断的情况下对List进行迭代和并发修改,因为迭代将在List的单独副本上进行。

Java CopyOnWriteArrayList构造函数

  • CopyOnWriteArrayList()–创建一个空列表。

  • CopyOnWriteArrayList(Collection <?extends E> c)–创建一个包含指定集合元素的列表,其顺序由集合的迭代器返回。

  • CopyOnWriteArrayList(E [] toCopyIn)–创建一个包含给定数组副本的列表。

Java示例,创建一个CopyOnWriteArrayList

这是一个简单的示例,显示了如何创建CopyOnWriteArrayList并向其中添加元素。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new CopyOnWriteArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    System.out.println("List elements- " + carList);
  }
}

输出:

List elements- [Audi, Jaguar, Mini Cooper, BMW]

CopyOnWriteArrayList返回故障保护迭代器

Java中的CopyOnWriteArrayList返回的迭代器是故障安全的,这意味着即使在创建迭代器后随时对列表进行结构修改,也可以保证迭代器不会引发ConcurrentModificationException。
为CopyOnWriteArrayList创建一个迭代器时,它将获得要迭代的基础数组的不可变副本。该数组在迭代器的生命周期内永不改变,因此是不可能的。
但请注意,由于迭代是在单独的副本上完成的,因此在迭代过程中不会反映出CopyOnWriteArrayList中的任何修改。

CopyOnWriteArrayList迭代示例

我们来看一个CopyOnWriteArrayList中的迭代示例。为了更清楚起见,我们先迭代一个ArrayList,同时另一个线程同时修改它,以查看ArrayList发生了什么,然后我们使用CopyOnWriteArrayList看到相同的示例。

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new ArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    Thread t1 = new Thread(new ItrClass(carList));
    Thread t2 = new Thread(new ModClass(carList));
    t1.start();
    t2.start();
    try {
      t1.join();
      t2.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("List elements in Main- " + carList);
  }
}

// Thread class for iteration
class ItrClass implements Runnable{
  List<String> carList; 
  public ItrClass(List<String> carList){
    this.carList = carList;
  }
  @Override
  public void run() {
    Iterator<String> i = carList.iterator(); 
    while (i.hasNext()){ 
      System.out.println(i.next()); 
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } 
    }     
  }
}

//Thread class for modifying list
class ModClass implements Runnable{
  List<String> carList; 
  public ModClass(List<String> carList){
    this.carList = carList;
  }
  @Override
  public void run() {
    System.out.println("Adding new value to the list"); 
    carList.add("Mercedes");  
  }     
}

输出:

Adding new value to the list
Audi
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at com.theitroad.ItrClass.run(CopyList.java:41)
	at java.base/java.lang.Thread.run(Thread.java:844)
List elements in Main- [Audi, Jaguar, Mini Cooper, BMW, Mercedes]

从ArrayList可以看到,如果列表在迭代时被修改,则抛出ConcurrentModificationException。

使用CopyOnWriteArrayList

现在,在同一示例中,我们可以将ArrayList更改为CopyOnWriteArrayList。

List<String> carList = new CopyOnWriteArrayList<String>();

与该输出是

Adding new value to the list
Audi
Jaguar
Mini Cooper
BMW
List elements in Main- [Audi, Jaguar, Mini Cooper, BMW, Mercedes]

如我们现在所见,不会引发ConcurrentModificationException,但是迭代器不会在迭代新的元素时显示新添加的元素。

CopyOnWriteArrayList中不允许使用迭代器的添加,删除方法

制作新副本使我们可以方便地迭代List,而不必担心ConcurrentModificationException,但与此同时,迭代器的元素更改操作(如remove,set和add在CopyOnWriteArrayList中是不支持的)。这些方法抛出UnsupportedOperationException。

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new CopyOnWriteArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    Iterator<String> itr = carList.iterator(); 
    while (itr.hasNext()){ 
      String str = itr.next();
      if(str.equals("Jaguar")) {
        // removing using iterator's remove method
        itr.remove();
      }
    }
  }
}

输出:

Exception in thread "main" java.lang.UnsupportedOperationException 
at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1117)
at com.theitroad.CopyList.main(CopyList.java:21)

如我们所见,在这里使用迭代器的remove方法会导致抛出UnsupportedOperationException。

在Java中使用CopyOnWriteArrayList的优缺点

当遍历操作的次数大于突变次数时,CopyOnWriteArrayList的性能会很好,因为我们无需显式同步CopyOnWriteArrayList即可在多线程环境中对其进行迭代。

通常,使用CopyOnWriteArrayList会很昂贵,因为增加了在发生突变操作时创建副本并不断更改基础数组的任务。

即使在迭代过程中对列表进行了并发修改,也可以保证CopyOnWriteArrayList不会引发ConcurrentModificationException。同时,迭代器的元素更改操作不受支持。