线程池死锁问题探究
问题可以简化为以下描述,由于数据量较大,单线程计算的时候可能耗费时间较长,所以采用多线程分别对每一条数据计算,然后由主线程汇总其他线程计算的结果。
思路如下:主线程创建一个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);
}
}