接口CompletionService的功能是以异步的方式一边生产新的任务,一边处理已经完成任务的结果,这样可以将执行任务与处理任务分离开来进行处理。使用submit执行任务,使用take取得已完成的任务,并按照完成这些任务的时间顺序处理他们的结果。
CompletionService介绍
接口CompletionService的结构比较简单,仅有一个实现类ExecutorCompletionService,该类的构造方法如下:
ExecutorCompletionService(Executor executor)
ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)
6.2 使用CompletionService解决Future缺点
上一篇说过接口Future具有阻塞同步性。地址
这样的代码运行效率会大打折扣,接口CompletionService可以解决这样的问题。
本例中使用CompletionService接口中的take()方法,它的主要作用就是取得Future对象,方法声明如下:
public Future take() throws InterruptedException
public class MyCallable implements Callable<String> {
private String username;
private long sleepValue;
public MyCallable(String username, long sleepValue) {
super();
this.username = username;
this.sleepValue = sleepValue;
}
public String call() throws Exception {
System.out.println(username);
Thread.sleep(sleepValue);
return "return"+username;
}
}
........................
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable callable1 = new MyCallable("第1个",5000);
MyCallable callable2 = new MyCallable("第2个",4000);
MyCallable callable3 = new MyCallable("第3个",3000);
MyCallable callable4 = new MyCallable("第4个",2000);
MyCallable callable5 = new MyCallable("第5个",1000);
List<Callable> callableList = new ArrayList<Callable>();
callableList.add(callable1);
callableList.add(callable2);
callableList.add(callable3);
callableList.add(callable4);
callableList.add(callable5);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,5,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
CompletionService csRef = new ExecutorCompletionService(executor);
for(int i = 0;i<5;i++) {
csRef.submit(callableList.get(i));
}
for(int i =0;i<5;i++) {
System.out.println("等待打印第"+(i+1)+"个返回值");
System.out.println(csRef.take().get());
}
}
}
运行结果:
第1个
第3个
第5个
第2个
第4个
等待打印第1个返回值
return第5个
等待打印第2个返回值
return第4个
等待打印第3个返回值
return第3个
等待打印第4个返回值
return第2个
等待打印第5个返回值
return第1个
从打印的结果看,CompletionService完全解决了Future阻塞的特性,也就是使用CompletionService接口后,哪个任务先执行完,哪个任务的返回值就就先打印。
不过,如果CompletionService接口中如果当前没有任务被执行完,则csRef.take().get()方法还是呈阻塞性。
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable callable1 = new MyCallable("第1个",5000);
MyCallable callable2 = new MyCallable("第2个",4000);
MyCallable callable3 = new MyCallable("第3个",3000);
MyCallable callable4 = new MyCallable("第4个",2000);
MyCallable callable5 = new MyCallable("第5个",1000);
List<Callable> callableList = new ArrayList<Callable>();
callableList.add(callable1);
callableList.add(callable2);
callableList.add(callable3);
callableList.add(callable4);
callableList.add(callable5);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,5,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
CompletionService csRef = new ExecutorCompletionService(executor);
for(int i = 0;i<5;i++) {
csRef.submit(callableList.get(i));
}
for(int i =0;i<6;i++) {
System.out.println("等待打印第"+(i+1)+"个返回值");
System.out.println(csRef.take().get());
}
}
}
................................
运行结果:
第2个
第1个
第3个
第4个
等待打印第1个返回值
第5个
return第5个
等待打印第2个返回值
return第4个
等待打印第3个返回值
return第3个
等待打印第4个返回值
return第2个
等待打印第5个返回值
return第1个
等待打印第6个返回值
循环次数变为6次,永久在阻塞状态
6.3 使用take()方法
方法take()取得最先完成任务的Future对象,谁执行时间最短谁最先返回。
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//take方法:获取并移除表示下一个已完成任务的Future,如果目前不存在这样的任务,则等待
ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executorService);
for(int i = 0;i<10;i++) {
csRef.submit(new Callable<String>() {
@Override
public String call() throws Exception {
long sleepValue =(int)(Math.random()*1000);
System.out.println("sleep="+sleepValue+" "+Thread.currentThread().getName());
Thread.sleep(sleepValue);
return "完成时间"+sleepValue+" "+Thread.currentThread().getName();
}
});
}
for(int i= 0;i<10;i++) {
System.out.println(csRef.take().get());
}
}
}
运行结果:
sleep=949 pool-1-thread-3
sleep=200 pool-1-thread-8
sleep=478 pool-1-thread-5
sleep=382 pool-1-thread-9
sleep=503 pool-1-thread-1
sleep=94 pool-1-thread-7
sleep=996 pool-1-thread-4
sleep=436 pool-1-thread-2
sleep=902 pool-1-thread-6
sleep=941 pool-1-thread-10
完成时间94 pool-1-thread-7
完成时间200 pool-1-thread-8
完成时间382 pool-1-thread-9
完成时间436 pool-1-thread-2
完成时间478 pool-1-thread-5
完成时间503 pool-1-thread-1
完成时间902 pool-1-thread-6
完成时间941 pool-1-thread-10
完成时间949 pool-1-thread-3
完成时间996 pool-1-thread-4
从运行结果来看,方法take()是按任务执行的速度,从快到慢的顺序获得Future对象,因为打印的时间是从小到大。
6.4 使用poll()方法
方法poll()的作用是获取并移除表示下一个已完成任务的Future,如果不存在这样的任务,则返回null,方法poll()无阻塞的效果。
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executorService);
for(int i =0;i<1;i++) {
csRef.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("3秒过后了");
return "我是返回值";
}
});
for(int i1 =0;i1<1;i1++) {
System.out.println(csRef.poll().get());
}
}
}
}
运行结果:
Exception in thread "main" java.lang.NullPointerException
at Test/cn.yu.completionservice.Test2.main(Test2.java:27)
3秒过后了
调用完csRef.poll()后将Future移除了,再调用get()方法,就报空指针异常。
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executorService);
for(int i =0;i<1;i++) {
csRef.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("3秒过后了");
return "我是返回值";
}
});
for(int i1 =0;i1<1;i1++) {
System.out.println(csRef.poll());
}
}
}
}
运行结果:
null
3秒过后了
6.5 使用poll(long timeout,TimeUnit unit )方法
方法Futurepoll(long timeout,TimeUnit unit)的作用是等待指定的timeout时间,在timeout时间之内获取到值时立即向下继续执行,如果超时也立即向下执行。
package cn.yu.completionservice;
import java.util.concurrent.Callable;
public class MyCallableA implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(5000);
System.out.println("MyCallableA"+System.currentTimeMillis());
return "returnA";
}
}
........................
public class MyCallableB implements Callable<String>{
@Override
public String call() throws Exception {
Thread.sleep(10000);
System.out.println("MyCallableB"+System.currentTimeMillis());
return "returnB";
}
}
...............................
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallableA callableA = new MyCallableA();
MyCallableB callableB = new MyCallableB();
Executor executor = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executor);
csRef.submit(callableA);
csRef.submit(callableB);
for(int i = 0;i<2;i++) {
System.out.println("zzzzzzzzzzzzzz"+" "+csRef.poll());
}
System.out.println("main end");
}
}
运行结果:
zzzzzzzzzzzzzz null
zzzzzzzzzzzzzz null
main end
MyCallableA1556281382612
MyCallableB1556281387612
返回两个null,因为线程还没开始执行就被清除了。
再看带参数的poll方法:
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallableA callableA = new MyCallableA();
MyCallableB callableB = new MyCallableB();
Executor executor = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executor);
csRef.submit(callableA);
csRef.submit(callableB);
for(int i = 0;i<2;i++) {
System.out.println("zzzzzzzzzzzzzz"+" "+csRef.poll(6,TimeUnit.SECONDS).get());
}
System.out.println("main end");
}
}
运行结果:
MyCallableA1556281647270
zzzzzzzzzzzzzz returnA
MyCallableB1556281652271
zzzzzzzzzzzzzz returnB
main end
如果方法poll()中timeout的值如果小于任务执行的时间,则返回值就是null.
6.6 类CompletionService与异常
使用CompletionService执行任务的过程中不可避免会出现各种情况的异常,下面来实验一下CompletionService对异常处理的流程。
6.7 方法Future <> submit(Runnable task,V result)
参数V是submit()方法的返回值。
package cn.yu.completionservice;
public class Userinfo {
private String username;
private String password;
public Userinfo() {
super();
}
public Userinfo(String username,String password) {
super();
this.password = password;
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
....................................
public class MyRunnable implements Runnable {
private Userinfo userinfo;
public MyRunnable(Userinfo userinfo) {
super();
this.userinfo = userinfo;
}
@Override
public void run() {
userinfo.setUsername("usernamevalue");
userinfo.setPassword("passwordValue");
System.out.println("run 运行了");
}
}
........................
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Userinfo userinfo = new Userinfo();
MyRunnable myRunnable = new MyRunnable(userinfo);
Executor executor = Executors.newCachedThreadPool();
CompletionService csRef = new ExecutorCompletionService(executor);
Future<Userinfo> future = csRef.submit(myRunnable,userinfo);
System.out.println(future.get().getUsername()+" "+future.get().getPassword());
}
}
运行结果:
run 运行了
usernamevalue passwordValue