Java代码竞争条件多线程?

时间:2017-01-13 14:43:33

标签: java multithreading synchronization

我有一些代码,我想知道在多线程环境中我是否会丢失数据......

以下是示例代码:

public class TestingJavaThreading {
    private final Map<String, Set<String>> data = Maps.newConcurrentMap();
    private final HttpClient client;
    private final AsyncDataProvider provider;
    private final String baseUrl;

    // This method is called first...
    public void init(String code) {
        // We initialise the set to ensure it doesn't throw a null pointer exception or something weird...
        data.put(code, Sets.newConcurrentHashSet());

        // We tell the provider we're interested in data...
        provider.subscribeToDataFrom(code);

        // This HTTP call may take long time, and we can't afford losing data, that's why we subscribed beforehand in the previous line...
        List<String> elements = client.request(baseUrl + code);

        // We add all of the new elements, meanwhile some elements may have been added by "onMessageFromProvider"
        data.get(code).addAll(elements);

        data.get(code)
            .stream()
            .map( /* some transformations here, whatever... */)
            .forEach(e -> System.out.println(e));

        // Now we've printed the merged data from "onMessageFromProvider" + the HTTP call
        // We remove the element from the map, so now we only receive data from "onMessageFromProvider"
        data.remove(code); 
    }

    public void onMessageFromProvider(String code, String element) {
        final Set<String> newSet = data.computeIfPresent(code, (k, v) -> {
          v.add(element);
          return v;
        });

        if (newSet == null) {
            // Do something else...
        }
    }
}

基本上,被调用的初始方法是init。步骤是这样的:

  1. 初始化CHM以确保其包含数据
  2. 我们有一个提供商,可以实时提供有关该元素的信息,但它不提供过去的数据。当数据来自提供者时,它调用方法“onMessageFromProvider”
  3. 为了获取项目的先前数据,我们需要进行单独的HTTP调用,然后将来自“onMessageFromProvider”的数据与来自HTTP调用的结果合并。完成之后,我们可以完全依赖“onMessageFromProvider”正在做的事情
  4. 一旦我们得到HTTP调用的结果,我们将它与来自“onMessageFromProvider”的数据合并,同时,我们应用转换,并打印生成的合并集
  5. 现在我们删除了地图密钥,因此我们可以完全依赖“onMessageFromProvider”正在做的事情
  6. 在步骤(3)运行时,这是否会导致数据丢失?我该如何解决?我应该在哪里放置更多代码,以尽可能少地依赖synchronished

    所以恢复,我的目标是永远不会丢失数据,我想确保我的算法100%保证。

    对于loooong帖子感到抱歉,希望它有意义。

    更新

    根据输入,我用实际样本更新代码,目前看起来像这样:

    public class Main {
    
      public static void main(String[] args) throws InterruptedException {
        new Main().init("X");
      }
    
      public void init(String code) throws InterruptedException {
        subscribeToDataFrom(code);
    
        CompletableFuture
            .supplyAsync(getDataFromHttpRequest());
      }
    
      private Supplier<Set<String>> getDataFromHttpRequest() {
        return () -> {
          Set<String> resultsToReturn = Sets.newHashSet();
          try {
            resultsToReturn.add("B");
            resultsToReturn.add("C");
            resultsToReturn.add("D");
            resultsToReturn.add("E");
            resultsToReturn.add("F");
            Thread.sleep(1000); // Simulate it is a slow request...
          } catch (Exception ex) {}
    
          return resultsToReturn;
        };
      }
    
      private void subscribeToDataFrom(String code) {
        Runnable r = () -> {
          while (true) {
            onMessageFromProvider(code, UUID.randomUUID().toString());
          }
        };
    
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
      }
    
      public void onMessageFromProvider(String code, String element) {
        // Here how do I create the completable future for usage in the previous CompletableFuture????
    
        final Set<String> newSet = data.computeIfPresent(code, (k, v) -> {
          v.add(element);
          return v;
        });
    
        if (newSet == null) {
          System.out.println("Ok, now I can do something different with: " + element);
        }
      }
    }
    

1 个答案:

答案 0 :(得分:0)

CompletableFuture类,具有组合和执行不同任务的方法

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html

  • 为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
  • 使用ForkJoinPool.commonPool()执行没有显式Executor参数的所有异步方法(除非它不支持至少两个的并行级别,在这种情况下,创建一个新的Thread来运行每个任务)。为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口CompletableFuture.AsynchronousCompletionTask的实例。
  • 所有CompletionStage方法都是独立于其他公共方法实现的,因此一个方法的行为不会受到子类中其他方法的覆盖的影响。

例如:

  CompletableFuture<String> httpTask = CompletableFuture.supplyAsync(
                                 () -> new HttpTask(httpClient));

  Set<String> result = httpTask.thenApply(elements -> //onMessageProvider method       
                   // maybe you can create a Callable class with the logic)

          .thenApply(mergedElements -> //remove code).get();
          //or try another method


  class HttpTask extends Callable<List<String>>{

    private HttpClient client;

    public HttpTask(HttpClient client){
        this.client = client;
    }

    @Override
    public List<String> call() throws Exception {
      return client.httpCall(...);
    }
   }