【问题标题】:Static Collection update inside CompletableFuture#runAsyncCompletableFuture#runAsync 中的静态集合更新
【发布时间】:2018-06-13 00:35:11
【问题描述】:

前提条件(通用描述)

1.静态类字段

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

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

在内部调用 static void main(String args[])方法

3. 元素添加到来自 step2

runAsync 调用内部的 someCollection

代码sn -p (具体说明)

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&lt;String&gt;?

【问题讨论】:

  • 停止使用可变静态变量。可变静态是邪恶的!!!
  • 除了@lance-java 所说的之外,您的问题实际上是ArrayList 不是线程安全的,并且您没有任何同步来访问它。所以你正在破坏它的内部数据结构。
  • @DidierL 谢谢你的提示,我已经开始使用ConcurrentSkipListSet&lt;String&gt; 看起来可以吗?
  • 我不知道你的要求,但你应该看看Is there a concurrent List in Java's JDK?ConcurrentLinkedQueue 可能更合适。
  • @DidierL 谢谢,我会调查一下

标签: java static field updating completable-future


【解决方案1】:

基于代码sn-p:

  • 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()));

【讨论】:

    【解决方案2】:

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

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

    同步示例:

    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);
      }
      //...
    }
    

    如果您期望有很多用户 ID,我假设使用 Collections.newSetFromMap(new ConcurrentHashMap() 最快。否则,您会熟悉 ConcurrentSkipListSet。

    volatile 在这里是一个不好的选择。易失性保证可见性,但不保证原子性。 volatile使用的典型例子有

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

    在这种情况下,您只执行一次写入/读取操作。由于“a”是易失的,因此可以保证每个线程“看到”(读取)正好是 1 或 2。 另一个(坏例子):

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

    这里我们做增量操作(读和写),a 可能有不同的结果,因为我们没有原子性。这就是为什么有 AtomicInteger、AtomicLong 等的原因。在您的情况下,所有线程都会看到写入值 id,但它们会写入不同的值,如果您在 ArrayList 的“add”方法内部看到,您会看到类似的内容:

    elementData[size++] = e;
    

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

    【讨论】:

      【解决方案3】:

      就线程安全而言,变量是否为静态并不重要。 重要的是

      1. 线程间共享状态的可见性。
      2. 当多个线程通过类方法使用类对象时的安全性(保留类不变量)。

      从可见性的角度来看,您的代码示例很好,因为ids 是静态的,将在类创建期间初始化。但是最好将其标记为finalvolatile,具体取决于ids 引用是否可以更改。但是违反了安全性,因为ArrayList 在设计上并未在多线程环境中保留其不变量。所以你应该使用一个专为多线程访问而设计的集合。 This 主题应该有助于选择。

      【讨论】:

        猜你喜欢
        • 2018-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多