Java并发编程之CompletionServie的使用

接口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个返回值
return5个
等待打印第2个返回值
return4个
等待打印第3个返回值
return3个
等待打印第4个返回值
return2个
等待打印第5个返回值
return1

从打印的结果看,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个返回值
第5return5个
等待打印第2个返回值
return4个
等待打印第3个返回值
return3个
等待打印第4个返回值
return2个
等待打印第5个返回值
return1个
等待打印第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
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值