【并发编程】- 线程池使用Future获取返回值的阻塞特性

161 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

Future缺点

线程执行代码如下:

@Slf4j
public class FutureCallable implements Callable<String> {

    private String username;
    private Long sleepTime;

    public FutureCallable(String username,Long sleepTime){
        super();
        this.username=username;
        this.sleepTime=sleepTime;
    }

    @Override
    public String call() throws Exception {
        log.info("用户名:{}",username);
        Thread.sleep(sleepTime);
        return username;
    }
}

运行类代码如下:

@Slf4j
public class FutureRun {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
        FutureCallable g1 = new FutureCallable("G1", 5000L);
        FutureCallable g2 = new FutureCallable("G2", 4000L);
        FutureCallable g3 = new FutureCallable("G3", 3000L);
        FutureCallable g4 = new FutureCallable("G4", 2000L);
        FutureCallable g5 = new FutureCallable("G5", 1000L);

        ArrayList<Callable> callableArrayList = new ArrayList<>();
        callableArrayList.add(g1);
        callableArrayList.add(g2);
        callableArrayList.add(g3);
        callableArrayList.add(g4);
        callableArrayList.add(g5);

        ArrayList<Future> futureArrayList = new ArrayList<>();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
        for (int i = 0; i < 5 ; i++) {
            futureArrayList.add(threadPoolExecutor.submit(callableArrayList.get(i)));
        }
        log.info("执行时间:{}",System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            try {
                log.info("线程执行返回值:{},执行时间:{}",futureArrayList.get(i).get(),System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

    }
}

运行结果如下:

17:54:39.330 [pool-1-thread-2] INFO com.ozx.concurrentprogram.executor.service.FutureCallable - 用户名:G2

17:54:39.330 [pool-1-thread-1] INFO com.ozx.concurrentprogram.executor.service.FutureCallable - 用户名:G1

17:54:39.331 [pool-1-thread-5] INFO com.ozx.concurrentprogram.executor.service.FutureCallable - 用户名:G5

17:54:39.330 [pool-1-thread-4] INFO com.ozx.concurrentprogram.executor.service.FutureCallable - 用户名:G4

17:54:39.330 [pool-1-thread-3] INFO com.ozx.concurrentprogram.executor.service.FutureCallable - 用户名:G3

17:54:39.330 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 执行时间:1659952479323

17:54:44.349 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 线程执行返回值:G1,执行时间:1659952484349

17:54:44.350 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 线程执行返回值:G2,执行时间:1659952484350

17:54:44.350 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 线程执行返回值:G3,执行时间:1659952484350

17:54:44.351 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 线程执行返回值:G4,执行时间:1659952484351

17:54:44.351 [main] INFO com.ozx.concurrentprogram.executor.controller.FutureRun - 线程执行返回值:G5,执行时间:1659952484351

从运行结果看出Future接口的缺点,根据这个情况,jdk1.5版本后提供了CompletionService接口可以解决这个问题。

CompletionService使用

​ 接口CompletionService的功能是以异步的方式一边生产新的任务,一边处理已完成任务,一边处理已完成任务的结果,这样可以将执行任务与处理任务分离开来进行处理,使用submit执行任务,使用take取得已完成的任务,并按照完成任务的时间顺序处理它们的结果。

查看CompletionService结构能看出,只有一个实现类ExecutorCompletionService,从它的构造方法的声明中可以发现,类ExecutorCompletionService需要依赖于Executor对象,大部分的实现是使用线程池ThreadExecutor对象。