CompletableFuture#runAsync中的静态集合更新

时间:2018-01-03 10:33:25

标签: java static field updating completable-future

前提条件(通用描述)

1。静态类字段

static List<String> ids = new ArrayList<>();

2。 CompletableFuture#runAsync(Runnable runnable,Executor executor)

在内部召唤 static void main(String args[])方法

来自 step2

someCollection来自runAsync内的

3。元素

代码段(具体说明)

private static List<String> ids = new ArrayList<>();

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //...
    final List<String> lines = Files.lines(path).collect(Collectors.toList());
    for (List<String> lines : CollectionUtils.split(1024, lines)) {
         CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
             List<User> users = buildUsers();
             populate(users);
         }, executorService);

        futures.add(future);
    }

    private static void populate(List<User> users){
       //...
       ids.add(User.getId);
       //...
    }
}

问题描述:

据我从并发的角度理解, 静态变量不能在线程之间共享,因此数据可能会以某种方式丢失。

是否应更改为volatile或使用它是合理的 ConcurrentSkipListSet<String>

3 个答案:

答案 0 :(得分:3)

基于代码段:

    此处不需要
  • volatile,因为它在参考级别上工作,而任务不更新集合对象的引用,它们会改变其状态。是否会更新引用,可能已使用volatileAtomicReference

  • 静态对象可以在线程之间共享,但该对象必须是线程安全的。并发收集将完成轻载到中载的工作。

但现代的方法是使用流而不是使用共享集合:

List<CompletableFuture<List<String>>> futures = lines.stream()
        .map(line -> CompletableFuture.supplyAsync(() -> buildUsers().stream()
                                                                     .map(User::getId)
                                                                     .collect(Collectors.toList()),
             executorService))
        .collect(Collectors.toList());

ids.addAll(futures.stream()
                  .map(CompletableFuture::join)
                  .flatMap(List::stream)
                  .collect(Collectors.toList()));

答案 1 :(得分:3)

在您的特定情况下,有一些方法可以保证ID的线程安全:

  1. 使用线程安全集合(例如,ConcurrentSkipListSet,CopyOnWriteArrayList,Collections.synchronizedList(new ArrayList(),Collections.newSetFromMap(new ConcurrentHashMap());
  2. 使用如下所示的同步。
  3. 同步的例子:

    private static synchronized void populate(List<User> users){
      //...
      ids.add(User.getId);
      //...
    }
    
    private static void populate(List<User> users){
      //...
      synchronized (ids) {
          ids.add(User.getId);
      }
      //...
    }
    

    我认为使用Collections.newSetFromMap(新的ConcurrentHashMap()是最快的,如果你期望很多用户id。否则,你会熟悉ConcurrentSkipListSet。

    volatile是一个糟糕的选择。易失性保证了可见性,但不保证原子性。挥发性使用的典型例子是

     volatile a = 1
    
     void threadOne() {
          if (a == 1) {
               // do something
          }
     }
    
     void threadTwo() {
          // do something 
          a = 2
     }
    

    在这种情况下,您只进行一次写/读操作。作为&#34; a&#34;是不稳定的,然后保证每个线程&#34;看&#34; (读)完全正好1或2。 另一个(坏例子):

     void threadOne() {
          if (a == 1) {
               // do something
               a++;
          }
     }
    
     void threadTwo() {
          if (a == 1) {
               // do something
               a = 2
          } else if (a == 2) {
               a++
          }
     }
    

    这里我们做增量操作(读和写)并且可能有不同的结果,因为我们没有原子性。这就是为什么有AtomicInteger,AtomicLong等等。在你的情况下,所有线程都会看到写入值ids,但是他们会写出不同的值,如果你看到内部&#34;添加&#34;在ArrayList的方法中,您将看到如下内容:

    elementData[size++] = e;
    

    所以没有人保证大小值的原子性,你可以在一个数组单元中写入不同的id。

答案 2 :(得分:2)

就线程安全而言,变量是否静态无关紧要。 重要的是

  1. 线程之间共享状态的可见性。
  2. 安全(保留类不变量)当多个线程通过类方法使用类对象时。
  3. 从可见性角度来看,您的代码示例很好,因为ids是静态的,并且会在类创建时初始化。但最好将其标记为finalvolatile,具体取决于是否可以更改ids引用。但是违反了安全性,因为ArrayList不能通过设计在多线程环境中保留其不变量。因此,您应该使用专为多线程访问而设计的集合。 This主题应该有助于选择。