【java并发】CompletionService深度解读,并采用Runnable实现同样机制

1 例子

10个任务异步并发执行,如果有任务完成,则实时获取任务结果。

/**
 * 多任务异步并发执行,并实时获取已完成任务的结果
 * 
 * @author chaozai
 * @date 2019年6月21日
 *
 */
public class CompletionServiceTest {
    
    public static void main(String[] args) {
	//线程池
	ExecutorService exec = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
		new LinkedBlockingQueue<Runnable>());
	//阻塞队列
	BlockingQueue<Future<Integer>> queue = new LinkedBlockingDeque<Future<Integer>>(10);
	//执行任务服务
	CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(exec, queue);
	long start = System.currentTimeMillis();
	System.out.println("start:" + DateFormatUtils.format(start, "yyyy-MM-dd hh:mm:ss:SSS"));
	// 提交10次任务
	for (int i = 0; i < 10; i++) {
	    completionService.submit(new Task());
	}
	int totalTime = 0;
	//提取10次任务结果
	for (int i = 0; i < 10; i++) {
	    try {
		Future<Integer> f = completionService.take();
		System.out.println(f.get());
		totalTime = totalTime+f.get();
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    } catch (ExecutionException e) {
		e.printStackTrace();
	    }
	}
	//任务各自执行时间的总和
	System.out.println("total time:"+totalTime);
	long end = System.currentTimeMillis();
	System.out.println("end:" + DateFormatUtils.format(end, "yyyy-MM-dd hh:mm:ss:SSS"));
	//所有任务实际消耗时间
	System.out.println("tasks coust ms:" + (end - start));
    }
}

/**
 * 任务:休眠随机秒数,并返回休眠时间
 * 
 * @author chaozai
 * @date 2019年6月21日
 *
 */
class Task implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
	int random = new Random().nextInt(1000);
	Thread.sleep(random);
	System.out.println(Thread.currentThread().getName() + " sleep " + random + " ms");
	return random;
    }

}

结果:发现如果任务同步按序执行,则需要4秒多,异步并发执行则只使用1秒,而且已完成的任务会实时返回结果

start:2019-06-21 05:13:20:335
pool-1-thread-6 sleep 47 ms
47
pool-1-thread-2 sleep 118 ms
118
pool-1-thread-4 sleep 147 ms
147
pool-1-thread-5 sleep 226 ms
226
pool-1-thread-10 sleep 353 ms
353
pool-1-thread-9 sleep 373 ms
373
pool-1-thread-7 sleep 560 ms
560
pool-1-thread-8 sleep 856 ms
856
pool-1-thread-3 sleep 931 ms
931
pool-1-thread-1 sleep 968 ms
968
total time:4579
end:2019-06-21 05:13:21:481
sync coust ms:1146

2 原理

  • 异步并发执行

这个很简单,就是多线程执行Callable类型的任务,查看ExecutorCompletionService源码:

任务提交:线程池executor执行了QueueingFuture任务,咦?这个没听过,原来是个ExecutorCompletionService内部定义的一个FutureTask子类。为何要单独定义一个Future呢?核心就在要和阻塞队列进行绑定,它重写了FutureTask的done方法,当任务执行结束,那么将任务结果放到阻塞队列中。

completionService.submit(new Task());

public Future<V> submit(Callable<V> task) {
	if (task == null) throw new NullPointerException();
	RunnableFuture<V> f = newTaskFor(task);
	executor.execute(new QueueingFuture(f));
	return f;
}
private class QueueingFuture extends FutureTask<Void> {
	QueueingFuture(RunnableFuture<V> task) {
	    super(task, null);
	    this.task = task;
	}
	protected void done() { completionQueue.add(task); }
	private final Future<V> task;
}
  • 已完成的任务会实时返回结果

ExecutorCompletionService中take方法,实际上是调用了阻塞队列的take方法:

completionService.take();

public Future<V> take() throws InterruptedException {
	return completionQueue.take();
}

再看看阻塞队列LinkedBlockingDeque中take的实现:如果队列为空,即任务还未执行完,则进行等待,任务结束后会唤醒等待条件notEmpty,此时将结果返回。

public E take() throws InterruptedException {
	return takeFirst();
}

public E takeFirst() throws InterruptedException {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    E x;
	    //Removes and returns first element, or null if empty.
	    while ( (x = unlinkFirst()) == null)
		//if empty,then wait
		notEmpty.await();
	    return x;
	} finally {
	    lock.unlock();
	}
}

3 其他

ExecutorService.invokeAll也可以批量执行异步任务集合,但是它需要等待所有任务都执行完成,才会将结果一次性返回。所以看大家的需求是及时获取,还是等待全部完成(缺一不可)。

4 思考

那么自己能不能实现一个类似CompletionService的机制呢?用基础的Runnable实现?

上代码:

/**
 * 使用Runnable实现CompletionService机制,实时获取结果功能
 */
public class SelfCompletionServiceTest {

    public static void main(String[] args) {
        //线程池
        ExecutorService exec = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        long start = System.currentTimeMillis();
        System.out.println("start:" + DateFormatUtils.format(start, "yyyy-MM-dd hh:mm:ss:SSS"));
        // 提交10次任务
        for (int i = 0; i < 10; i++) {
            exec.execute(new Task());
        }
        int totalTime = 0;
        //提取10次任务结果
        for (int i = 0; i < 10; i++) {
            try {
                int result = Task.get();
                System.out.println(result);
                totalTime = totalTime + result;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //任务各自执行时间的总和
        System.out.println("total time:" + totalTime);
        long end = System.currentTimeMillis();
        System.out.println("end:" + DateFormatUtils.format(end, "yyyy-MM-dd hh:mm:ss:SSS"));
        //所有任务实际消耗时间
        System.out.println("tasks coust ms:" + (end - start));
    }
}

/**
 * 任务:休眠随机秒数,并返回休眠时间
 *
 * @author chaozai
 * @date 2019年6月21日
 */
class Task implements Runnable {

    private static BlockingQueue<Integer> OVER_JOB_QUEUE = new LinkedBlockingDeque<Integer>(10);
    private static byte[] LOCK = new byte[0];//创建最简易的lock

    public void run() {
        int random = new Random().nextInt(1000);
        try {
            Thread.sleep(random);
            add(random);
            System.out.println(Thread.currentThread().getName() + " sleep " + random + " ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void add(int number) {
        OVER_JOB_QUEUE.add(number);
    }

    public static int get() throws InterruptedException {
        return OVER_JOB_QUEUE.take();
    }
}
结果:和CompletionService一样,比串行执行,效率快了5倍以上!
start:2019-11-13 10:02:48:425
pool-1-thread-7 sleep 80 ms
80
pool-1-thread-4 sleep 345 ms
345
pool-1-thread-6 sleep 436 ms
436
pool-1-thread-8 sleep 498 ms
498
pool-1-thread-10 sleep 509 ms
509
pool-1-thread-2 sleep 577 ms
577
pool-1-thread-9 sleep 608 ms
608
pool-1-thread-1 sleep 722 ms
722
pool-1-thread-3 sleep 772 ms
772
pool-1-thread-5 sleep 813 ms
813
total time:5360
end:2019-11-13 10:02:49:367
tasks coust ms:942

思考:不要迷恋Future,CompletionService这些新花样,获取线程运行结果?实时提取最新结果集?这些靠最基本的Runnable和BlockingQueue,都能实现!实际上,他们也是基于这些实现的。

 

 

 


爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqchaozai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值