Java CopyOnWriteArraySet与示例

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

Java中的CopyOnWriteArraySet扩展了AbstractSet,后者又实现了Set接口,并且是java.util.concurrent包的一部分。 CopyOnWriteArraySet与Java Collections框架中其他Set实现的不同之处在于它是线程安全的。

Java中的CopyOnWriteArraySet内部实现

CopyOnWriteArraySet内部使用CopyOnWriteArrayList进行所有操作,并共享相同的基本属性。在Java中的CopyOnWriteArraySet类中,CopyOnWriteArrayList定义如下:

private final CopyOnWriteArrayList<E> al;

创建CopyOnwriteArraySet时,将初始化此CopyOnWriteArrayList字段并将其用于存储元素。

例如,当使用不带参数的构造函数创建CopyOnwriteArraySet时。

public CopyOnWriteArraySet() {
  al = new CopyOnWriteArrayList<E>();
}

CopyOnWriteArraySet的功能

本文中讨论的Java中的CopyOnWriteArraySet的一些功能是

  • CopyOnWriteArraySet是Set实现,因此不允许重复元素。

  • CopyOnWriteArraySet是线程安全的。

  • 由于CopyOnWriteArraySet在内部使用CopyOnWriteArrayList,所以就像在CopyOnWriteArrayList中一样,所有可变操作(添加,设置等)都将为基础数组创建一个单独的副本,这样就不会造成线程干扰。

  • Java中CopyOnWriteArraySet返回的迭代器是故障安全的,这意味着即使在创建迭代器后随时对Set进行结构修改,也可以保证迭代器不会引发ConcurrentModificationException。

  • 迭代器的元素更改操作(例如添加,删除)是不支持的,并引发UnsupportedOperationException。

Java CopyOnWriteArraySet构造函数

  • CopyOnWriteArraySet()–创建一个空集。

  • CopyOnWriteArraySet(Collection <?extends E> c)–创建一个包含指定集合的所有元素的集合。

创建CopyOnWriteArraySet的Java示例

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

public class ConcurrentSet {
  public static void main(String[] args) {
    Set<String> carSet = new CopyOnWriteArraySet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    carSet.add("BMW");
    carSet.add(null);
    for(String car : carSet) {
      System.out.println("Car- " + car);
    }
  }
}

输出:

Car- Audi
Car- Jaguar
Car- BMW
Car- Mini Cooper
Car- null

从输出中可以看到,即使将" BMW"添加了两次,也不允许在CopyOnWriteArraySet中进行重复操作,但只能存储一次。同样允许一个空值。

CopyOnWriteArraySet返回故障保护迭代器

Java中CopyOnWriteArraySet返回的迭代器是故障安全的,这意味着即使在创建迭代器后随时对Set进行结构修改,也可以保证迭代器不会引发ConcurrentModificationException。

为CopyOnWriteArraySet创建迭代器时,它将获得要迭代的基础数组的不可变副本。该数组在迭代器的生命周期内永不改变,因此是不可能的。
但请注意,由于迭代是在单独的副本上完成的,因此在迭代过程中不会反映出CopyOnWriteArraySet中的任何修改。

CopyOnWriteArraySet迭代示例

我们来看一个CopyOnWriteArraySet中的迭代示例。为了更清楚地说明,我们先对HashSet进行迭代,同时还要由另一个线程同时对其进行修改,以查看具有快速失败迭代器的HashSet发生了什么,然后我们将使用CopyOnWriteArraySet查看相同的示例。

public class SetItr {
  public static void main(String[] args) {
    Set<String> carSet = new HashSet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    Thread t1 = new Thread(new ItrSet(carSet));
    Thread t2 = new Thread(new ModSet(carSet));
    t1.start();
    t2.start();
    try {
      t1.join();
      t2.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

//Thread class for iteration
class ItrSet implements Runnable{
  Set<String> carSet; 
  public ItrSet(Set<String> carSet){
    this.carSet = carSet;
  }
  @Override
  public void run() {
    Iterator<String> i = carSet.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 Set
class ModSet implements Runnable{
  Set<String> carSet; 
  public ModSet(Set<String> carSet){
    this.carSet = carSet;
  }
  @Override
  public void run() {
    System.out.println("Adding new value to the Set"); 
    carSet.add("Mercedes");  
  }     
}

输出:

Adding new value to the Set
Audi
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1498)
	at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1521)
	at com.theitroad.ItrSet.run(SetItr.java:40)
	at java.base/java.lang.Thread.run(Thread.java:844)

如我们所见,在迭代HashSet时检测到结构修改,将引发ConcurrentModificationException。

使用CopyOnWriteArraySet
通过在相同的代码中将HashSet更改为CopyOnWriteArraySet并运行它。

Set<String> carSet = new CopyOnWriteArraySet<String>();

我们可以将输出作为

Adding new value to the Set
Audi
Jaguar
BMW
Mini Cooper

现在,不会引发ConcurrentModificationException,但由于迭代是在单独的副本上完成的,因此不会在迭代中显示添加的新值。

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

由于CopyOnWriteArraySet中元素的迭代是在单独的副本迭代器的元素更改操作(如remove is不支持)上完成的。这些方法抛出UnsupportedOperationException。

public class SetItr {
  public static void main(String[] args) {
    Set<String> carSet = new CopyOnWriteArraySet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    Iterator<String> itr = carSet.iterator(); 
    while (itr.hasNext()){ 
      String str = itr.next();
      if(str.equals("BMW")) {
        // 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.SetItr.main(SetItr.java:21)

有关CopyOnWriteArraySet的要点

  • CopyOnWriteArraySet最适合于集大小较小,只读操作多于可变操作的应用,并且我们需要防止遍历期间线程之间的干扰。

  • 交互操作(添加,设置,删除等)的成本很高,这是因为添加了创建基础数组副本的任务。

  • 即使在迭代过程中对Set进行了并发修改,也保证CopyOnWriteArraySet不会引发ConcurrentModificationException。同时,迭代器的元素更改操作(例如remove)是不支持的。