callable的get导致的线程阻塞与ExecutorCompletionService的执行顺序

下面代码,多线程内代码执行会各自执行,但当对future利用get获取结果时,会被get阻塞,阻塞顺序按照executor的提交顺序:c2/c3/c1。即首先get到c2的结果,这之中休眠3秒;然后get到c3的结果,这之中休眠1秒,最后get到c1的结果,这之中休眠2秒。get在主线程中完全阻塞住了多线程获取结果的时间。

ExecutorService es = Executors.newFixedThreadPool(3);
Callable<Integer> c1 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        return 11;
    }
};
Callable<Integer> c2 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);
        return 22;
    }
};
Callable<Integer> c3 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(1000);
        return 33;
    }
};
ArrayList<Callable> cs = new ArrayList<>();
cs.add(c2);
cs.add(c3);
cs.add(c1);
for (Callable c : cs) {
    Future<Integer> f = es.submit(c);
    try {
        System.out.println(f.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
es.shutdown();

利用ExecutorCompletionService的tack方法,可以按照线程各自执行完成的顺序,进行结果输出。如下面代码,输出顺序为:c3/c1/c2,这是因为,ExecutorCompletionService的tack方法不会在主线程阻塞,虽然提交顺序是c2/c3/c1,但会统筹所有线程休眠时间,按照时间依次执行,即:以项目启动时间为起点,1秒后执行c3,2秒后执行c1,3秒后执行c2,三个线程任务执行获取结果的时间间隔为1秒。

ExecutorService es = Executors.newFixedThreadPool(3);
ExecutorCompletionService<Integer> ecs = new ExecutorCompletionService<>(es);
Callable<Integer> c1 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        return 11;
    }
};
Callable<Integer> c2 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);
        return 22;
    }
};
Callable<Integer> c3 = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(1000);
        return 33;
    }
};
ArrayList<Callable> cs = new ArrayList<>();
cs.add(c2);
cs.add(c3);
cs.add(c1);
for (Callable c : cs) {
    ecs.submit(c);
}
for (int n = 0; n < cs.size(); n++) {//注意这里不用future去get,而是直接用ExecutorCompletionService的take结果去get,get的次数要与线程数一致,利用Callable的集合取确定线程数
    try {
        Integer i = ecs.take().get();//take:有就返回,没有就等到有再返回;poll无参:直接返回,没执行出结果则直接返回null;poll有参:有就返回,没有就等到入参定义的时间后返回执行结果,若还是没有执行到,则直接返回null
        System.out.println(i);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
es.shutdown();

在读下面代码前,先区分下线程池的几种关闭方式:
1、shutdown方法:将线程池状态置为SHUTDOWN。平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。

2、awaitTermination方法:接收人timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。

3、shutdownNow方法:将线程池状态置为STOP。跟shutdown()一样,先停止接收外部提交的任务,忽略队列里等待的任务,尝试将正在跑的任务interrupt中断,返回未执行的任务列表。

public class TestDemo2 {
    private static final int TIMES = 60;
    public static void main(String[] args) throws Exception {

        Callable<Integer> cal1 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(10000);
                return 11;
            }
        };

        Callable<Integer> cal2 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 22;
            }
        };

        Callable<Integer> cal3 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(7000);
                return 33;
            }
        };

        Callable<Integer> cal4 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 44;
            }
        };

        Callable<Integer> cal5 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 55;
            }
        };

        ExecutorService es = Executors.newFixedThreadPool(5);

        ExecutorCompletionService<Integer> service = new ExecutorCompletionService<>(es);

        ArrayList<Callable> list = new ArrayList<>();

        list.add(cal2);
        list.add(cal3);
        list.add(cal4);
        list.add(cal5);
        list.add(cal1);


        for (Callable c : list) {
            service.submit(c);
        }


        es.shutdown();//这里就是只有当线程池没有了正在执行的线程,且任务队列内没有任务后,可平滑关闭。
        
        int times = TIMES;
        while (true) {
            try {
                if (es.awaitTermination(1, TimeUnit.SECONDS)||times<=0){//此处表示,每秒检测下原线程池是否平滑关闭,或者当前是否满60秒了,满足条件,则跳到下面输出结果
                    break;
                }
                times--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        for (int i = 0; i < 5; i++) {
            System.out.println(service.poll().get());
        }

    }
}

最终,上面代码将在10秒后把结果按照各线程休眠时间确定执行顺序,然后直接一起输出:22、55、44、33、11。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值