Loading

线程池死锁问题探究

问题背景

问题可以简化为以下描述,由于数据量较大,单线程计算的时候可能耗费时间较长,所以采用多线程分别对每一条数据计算,然后由主线程汇总其他线程计算的结果。

思路如下:主线程创建一个CyclicBarrier,然后每个线程计算完成之后调用barrier.await();最后等待主线程汇总计算结果。代码如下。

为了方便,代码就使用了简写的方式

public class BlogThreadSum {
  private ExecutorService executorService = Executors.newFixedThreadPool(20);

  public static void main(String[] args) {
      BlogThreadSum threadSum = new BlogThreadSum();
      threadSum.doMain();
  }

  private void doMain() {
      List<String> baseList = new ArrayList<>();
      for (int i = 0; i < 10; i++) {
          baseList.add(i + "");
      }

      List<Future<Integer>> futures = new ArrayList<>();
      CyclicBarrier barrier = new CyclicBarrier(baseList.size(), () -> {
          int sum = 0;
          for (Future<Integer> future : futures) {
              try {
                  sum += future.get();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (ExecutionException e) {
                  e.printStackTrace();
              }
          }
          System.out.println("所有数据插入完成,最后一个插入的是线程:" + Thread.currentThread().getName());
          System.out.println("所有线程的计算结果为:" + sum);
      });

      for (String s : baseList) {
          Future<Integer> future = executorService.submit(() -> doInsert(s, barrier));
          futures.add(future);
      }
      System.out.println("主线程执行完毕");
  }

  private int doInsert(String s, CyclicBarrier barrier) throws InterruptedException {
      Random random = new Random();
      Thread.sleep(random.nextInt(100));
      System.out.println("=============>插入数据完成:" + s);
      try {
          barrier.await();
      } catch (BrokenBarrierException e) {
          e.printStackTrace();
      }
      return Integer.parseInt(s);
  }
}

这种代码看似能运算出结果,所有线程统计结果并返回到futures中,由于CyclicBarrier并不会阻塞主线程,所有我们可以看出主线程是最先执行完毕的。然后所有线程都执行完毕后,会执行CyclicBarrier的回调接口内容,但是就在此时我们会发现线程死锁了,原因就是当所有的线程都执行 barrier.await();之后,最后一个到达的线程就会直接执行yclicBarrier的回调接口的内容,但是此时此线程还没有执行return语句,也就是还没有返回值,而调用future.get();又会等待返回值的到来,所以就造成了相互等待,造成了死锁。有没有解决办法呢,当然有,以下两种解决方案都可以解决此问题,但是两种方案还是有差别的,一种会阻塞主线程,一种不会阻塞主线程,有最后一个计算完成的线程汇总所有线程的执行结果。

第一种方案:阻塞主线程,所有子线程完成之后,由主线程汇总所有子线程的计算结果

public class BlogThreadSum {
  private ExecutorService executorService = Executors.newFixedThreadPool(20);

  public static void main(String[] args) throws ExecutionException, InterruptedException {
      BlogThreadSum threadSum = new BlogThreadSum();
      threadSum.doMain();
  }
  private void doMain() throws ExecutionException, InterruptedException {
      List<String> baseList = new ArrayList<>();
      for (int i = 0; i < 10; i++) {
          baseList.add(i + "");
      }
      List<Future<Integer>> futures = new ArrayList<>();

      for (String s : baseList) {
          Future<Integer> future = executorService.submit(() -> doInsert(s));
          futures.add(future);
      }
      int sum = 0;
      for (Future<Integer> future : futures) {
          sum += future.get();
      }
      System.out.println("汇总的线程为:" + Thread.currentThread().getName() + ",所有线程的计算结果为:" + sum);
      System.out.println("主线程执行完毕");
  }


  private int doInsert(String s) throws InterruptedException {
      Random random = new Random();
      Thread.sleep(random.nextInt(100));
      System.out.println("=============>插入数据完成:" + s);
      return Integer.parseInt(s);
  }
}

第二种方案,所有线程计算完成之后,由最后一个计算完成的线程汇总结果

    private void doMain() {
      List<String> baseList = new ArrayList<>();
      for (int i = 0; i < 10; i++) {
          baseList.add(i + "");
      }

      AtomicInteger sum = new AtomicInteger(0);
      CyclicBarrier barrier = new CyclicBarrier(baseList.size(), () -> {
          System.out.println("所有数据插入完成,最后一个插入的是线程:" + Thread.currentThread().getName());
          System.out.println("汇总的线程为:" + Thread.currentThread().getName() + ",所有线程的计算结果为:" + sum);
      });

      for (String s : baseList) {
          executorService.submit(() -> doInsert(s, barrier, sum));
      }
      System.out.println("主线程执行完毕");
  }

  private int doInsert(String s, CyclicBarrier barrier, AtomicInteger sum) throws InterruptedException, BrokenBarrierException {
      Random random = new Random();
      Thread.sleep(random.nextInt(100));
      System.out.println("=============>插入数据完成:" + s);
      sum.addAndGet(Integer.parseInt(s));
      barrier.await();
      return Integer.parseInt(s);
  }
}

小结:以上就是多线程分批计算,然后汇总所有的线程的计算的结果的大致方案,但是要注意,如果CyclicBarrier和Future一起使用的话可能会造成死锁,具体的解决方案有以下两种,注意第二种方案中向线程之中传递的参数必须是线程安全的,否则可能会由于线程安全的问题造成数据的错误。

posted @ 2021-06-05 17:17  Philosophy  阅读(178)  评论(0编辑  收藏  举报