如何同步Java HashSet

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

这篇文章介绍了如何在Java中同步HashSet和HashSet的线程安全替代方法,该替代方法可以代替HashSet使用。

HashSet不是线程安全的

Java中的HashSet不是线程安全的,因为默认情况下它不同步。如果在多线程环境中使用HashSet,并且同时由多个线程访问它并且甚至由单个线程对其进行结构修改,则必须在外部进行同步。结构修改定义为添加或者删除一个或者多个元素,或者显式调整后备数组大小的任何操作;仅设置元素的值不是结构上的修改。

线程安全设置的选项

如果要在Java中同步HashSet或者寻找HashSet的线程安全替代方法,则有以下选项。

  • 使用Collections.synchronizedSet()同步Set –此方法返回由指定set支持的同步(线程安全)set。
  • 使用CopyOnWriteArraySet –另一个选择是使用java.util.concurrent包中的CopyOnWriteArraySet,它是Set的线程安全实现。 CopyOnWriteArraySet使用内部CopyOnWriteArrayList进行所有操作。对于所有变异操作(添加,设置,删除等),都会创建一个新副本,这就是使用它很昂贵的原因。

使用Collections.synchronizedSet()

我们可以使用Collections.synchronizedSet()方法同步HashSet。首先,我们将看到一个示例,如果在多线程环境中使用HashSet而不同步它会发生什么。

在Java代码中,创建了四个线程,每个线程向Set添加5个元素。完成所有线程后,Set size应该为20。

public class SetSynchro implements Runnable{
  private Set<String> numSet;
  public SetSynchro(Set<String> numSet){
    this.numSet = numSet;
  }
  
  public static void main(String[] args) {
    Set<String> numSet = new HashSet<String>();
    /// 4 threads
    Thread t1 = new Thread(new SetSynchro(numSet));
    Thread t2 = new Thread(new SetSynchro(numSet));
    Thread t3 = new Thread(new SetSynchro(numSet));
    Thread t4 = new Thread(new SetSynchro(numSet));
        
    t1.start();
    t2.start();
    t3.start();
    t4.start();
        
    try {
      t1.join();
      t2.join();
      t3.join();
      t4.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of Set is " + numSet.size());
  }

  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 5; i++){
      // adding thread name to make element unique
      numSet.add(i + str);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

输出:

in run methodThread-2
in run methodThread-0
in run methodThread-3
in run methodThread-1
Size of Set is 19

在一次运行中,大小为19,在另一次运行中为18,有时甚至为20,因此我们可以看到线程干扰使行为变得不可预测。因此,我们将使用相同的示例同步HashSet。

public class SetSynchro implements Runnable{
  private Set<String> numSet;

  public SetSynchro(Set<String> numSet){
    this.numSet = numSet;
  }

  public static void main(String[] args) {
    // Synchronized Set
    Set<String> numSet = Collections.synchronizedSet(new HashSet<String>());
    /// 4 threads
    Thread t1 = new Thread(new SetSynchro(numSet));
    Thread t2 = new Thread(new SetSynchro(numSet));
    Thread t3 = new Thread(new SetSynchro(numSet));
    Thread t4 = new Thread(new SetSynchro(numSet));
    
    t1.start();
    t2.start();
    t3.start();
    t4.start();
        
    try {
      t1.join();
      t2.join();
      t3.join();
      t4.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of Set is " + numSet.size());
  }

  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 5; i++){
      // adding thread name to make element unique
      numSet.add(i + str);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

输出:

in run methodThread-3
in run methodThread-2
in run methodThread-1
in run methodThread-0
Size of Set is 20

现在,每次HashSet的大小为20。

使用CopyOnWriteArraySet

具有线程安全Set的另一个选项是使用CopyOnWriteArraySet。让我们用一个简单的示例来查看它,在该示例中创建一个CopyOnWriteArraySet,然后对其进行迭代。迭代时,使用Set的remove方法删除元素,但仍不会引发ConcurrentModificationException。在输出中,我们可以看到迭代显示了所有元素,因为迭代是在CopyOnWriteArraySet的单独副本上完成的。

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

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> i = carSet.iterator(); 
    while (i.hasNext()){            
      carSet.remove("Jaguar");
      System.out.println(i.next()); 
    } 
    System.out.println("Set after removal" + carSet); 
  }
}

输出:

Audi
Jaguar
BMW
Mini Cooper
Set after removal[Audi, BMW, Mini Cooper]