用Callable实现在走完10个线程之后再进行操作

昨天一个碰到了一个题目:

如何编程,使得主线程在跑完十个线程之后,再进行后续操作。

看到这个问题,我的第一反应就是可以使用Callable来实现。因为实现线程有两种方式,实现Callable接口或者实现Runnable接口(至于继承Thread类,我觉得这个方式其实本质上还是实现了Runnable接口)。但是用Runnable实现线程,我们一般是不知道线程有没有跑完的,除非经过特殊处理(如使用CountDownLatch)(ps:使用线程池的submit()可以返回一个future,调用其isDone()方法也可以确认其是否完成,2018-05-08修改)。但是如果使用Callable实现线程,我们是可以在call()方法中返回一个值,来确认此线程跑完了。因此,我们可以在主线程中收集Callable的返回值,直到收集满了十个以后,再进行后续操作,没有收集满的话,就让主线程先休眠一会儿(这个只是初步想法,后续可以优化)。

现在,我们使用Callable来实现这个目的。

class MyCallable implements Callable<Integer>{
	private static int count = 0;
	private static Random random = new Random();
	private final int id = count ++;
	public Integer call() throws Exception {
		System.out.println("线程 \t" + id +"\t开始了");
		//模拟工作耗时
		int time = random.nextInt(3) + 1 ;
		System.out.println("线程 \t" + id +"\t耗时" + time + "秒");
		Thread.sleep(1000 * time);
		return id;
	}
}
/**
 * 2018-05-04
 * @author liujie
 *
 */
public class TestCallable {
	public static void main(String[] args) throws Exception{
		int SIZE = 10;
		int finished = 0;
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		//我们使用一个List来存放Callable的返回值
		List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
		for (int i = 0; i < SIZE; i++) {
			Thread.sleep(100);
			futures.add(cachedThreadPool.submit(new MyCallable()));
		}
		while (finished < SIZE) {
			for (Future<Integer> future : futures) {
				Integer id = future.get();
				System.out.println("主线程收集的到了 \t" + id + "\t线程的返回值");
				finished ++ ;
			}
		}
		System.out.println(SIZE + "\t个线程都跑完了,继续主线程的工作");
	}
}

如代码所示,我们在主线程中每隔0.1秒开启一个线程,一共开启十个,使用一个List收集callable的返回值,知道收集十个返回值为止。

控制台打印情况如下:

线程 	0	耗时1秒
线程 	1	耗时1秒
线程 	2	耗时3秒
线程 	3	耗时1秒
线程 	4	耗时1秒
线程 	5	耗时3秒
线程 	6	耗时3秒
线程 	7	耗时2秒
线程 	8	耗时1秒
线程 	9	耗时3秒
主线程收集的到了 	0	线程的返回值
主线程收集的到了 	1	线程的返回值
主线程收集的到了 	2	线程的返回值
主线程收集的到了 	3	线程的返回值
主线程收集的到了 	4	线程的返回值
主线程收集的到了 	5	线程的返回值
主线程收集的到了 	6	线程的返回值
主线程收集的到了 	7	线程的返回值
主线程收集的到了 	8	线程的返回值
主线程收集的到了 	9	线程的返回值
10	个线程都跑完了,继续主线程的工作
程序能够正常完成题目的要求,即主线程会在十个线程都完成之后再进行后续工作

但是,上面的代码其实忽略了一个重要的特性,那就是future.get()其实是阻塞的,也就是说当我们尝试用get获取返回值的时候,如果当时线程还没有跑完,这时候其实主线程就会一直等在这里,直到该线程跑完并返回一个值。

因此,以上代码可以优化成如下所示:

public class TestCallable {
	public static void main(String[] args) throws Exception{
		int SIZE = 10;
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		//我们使用一个List来存放Callable的返回值
		List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
		for (int i = 0; i < SIZE; i++) {
			Thread.sleep(100);
			futures.add(cachedThreadPool.submit(new MyCallable()));
		}
		for (Future<Integer> future : futures) {
			Integer id = future.get();
			System.out.println("主线程收集的到了 \t" + id + "\t线程的返回值");
		}
		System.out.println(SIZE + "\t个线程都跑完了,继续主线程的工作");
	}
}

控制台的情况也跟上面的一样。

还有一点值得注意的是,

主线程收集的到了 	0	线程的返回值
主线程收集的到了 	1	线程的返回值
主线程收集的到了 	2	线程的返回值
主线程收集的到了 	3	线程的返回值
主线程收集的到了 	4	线程的返回值
主线程收集的到了 	5	线程的返回值
主线程收集的到了 	6	线程的返回值
主线程收集的到了 	7	线程的返回值
主线程收集的到了 	8	线程的返回值
主线程收集的到了 	9	线程的返回值


其实线程9有可能比线程0先跑完,但是由于我们在创建Future的List的时候,是按照从0开始的,在future.get()获取返回值的时候,也是先从0开始。

其实线程9有可能比线程0先跑完,但是由于我们在创建Future的List的时候,是按照从0开始的,在future.get()获取返回值的时候,也是先从0开始。

其实要完成题目的要求,更好地是使用CountDownLatch来完成,后续我将会补上用CountDownLatch来完成的博客。

用CountDownLatch实现在走完10个线程之后再进行操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值