Runnable和Callable的区别
- Callable接口
- Runnable接口
- Runnable和Callable的区别
- 相同点
- 不同点
- 注意点
- Callable工作的Demo
- ExcutorService中的excutor和submit方法的区别
- 使用场景:
- FutureTask
- ExecutorCompletionService
- 示例代码
- 使用CompletionService维护结果
- 自己创建list维护执行结果
Callable接口
public interface Callable<V> {
V call() throws Exception;
}
Runnable接口
public interface Runnable {
public abstract void run();
}
Runnable和Callable的区别
相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用()启动线程;
不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点
Callable接口支持返回执行结果,此时需要调用()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
Callable工作的Demo
package com.callable.runnable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Created on 2016/5/18.
*/
public class CallableImpl implements Callable<String> {
public CallableImpl(String acceptStr) {
this.acceptStr = acceptStr;
}
private String acceptStr;
@Override
public String call() throws Exception {
// 任务阻塞 1 秒
Thread.sleep(1000);
return this.acceptStr + " append some chars and return it!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new CallableImpl("my callable test!");
FutureTask<String> task = new FutureTask<>(callable);
long beginTime = System.currentTimeMillis();
// 创建线程
new Thread(task).start();
// 调用get()阻塞主线程,反之,线程不会阻塞
String result = task.get();
long endTime = System.currentTimeMillis();
System.out.println("hello : " + result);
System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
}
}
ExcutorService中的excutor和submit方法的区别
两者都是将一个线程任务添加到线程池中并执行;
1、excutor没有返回值,submit有返回值,并且返回执行结果Future对象
2、excutor不能提交Callable任务,只能提交Runnable任务,submit两者任务都可以提交
3、在submit中提交Runnable任务,会返回执行结果Future对象,但是Future调用get方法将返回null(Runnable没有返回值)
使用场景:
使用多线程任务校验被保人数据;
1、每个被保人都是一个线程任务,
2、每个线程任务执行完都需要告诉主线程执行成功还是失败
3、这里需要submit提交Callable任务返回Future对象,并通过方法来获取执行结果
FutureTask
FutureTask实现了RunnableFuture,RunnableFuture既实现了Runnbale又实现了Futrue这两个接口;它两者的结合体
FutureTask又包装了Callable(如果是Runnable最终会转化为Callable);
FutureTask可以通过Thread包装来直接执行,也可以提交给ExcutorService来执行,并可以直接通过get方法来获取执行结果;
ExecutorCompletionService
使用ExecutorCompletionService提交任务后会将执行结果放到阻塞队列中,使用take方法会得到结果,哪个任务先执行完成就先获取到这个任务的执行结果;
原理:在ExecutorCompletionService维护了一个QueueingFuture(队列任务),当通过ExecutorCompletionService提交的任务执行完成后,将结果放入QueueingFuture中;然后通过take和poll方法获取执行结果时会阻塞线程,直到当QueueingFuture中有结果时就会立即返回;
如果自己维护一个list来存放future执行结果,会导致的问题是:这样通过方法来获取执行结果只能一个一个阻塞取出执行结果,如果后面的任务可能会先执行完成,后面的任务只有等待前面的任务执行完成得到结果后才能获取到结果,这样就浪费一定的时间在等待执行任务的结果上了。可以用ExecutorCompletionService来解决这个问题的。
总结:
1、自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。
2、使用CompletionService来维护处理线程的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。
3、CompletionService的实现是维护了一个保存Future的BlockingQueque。只有当这个Future的任务状态是结束的时候,才会加入到这个Queque中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。也就是先完成的必定先被取出,这样就减少了不必要的等待时间。
注意:使用Future获取多个任务的执行结果时,如果其中一个任务出现异常,就会直接中断不会获取后面任务的执行结果了,但是不会中断其他任务,其他任务会正常运行,只是结果无法获取了;如果需要中断其他任务就需要在捕获到异常后执行方法来关闭线程,也可以通过方法来中断
ExecutorService中shutdownNow和shutdown的区别:
shutdownNow表示立刻关闭线程池并中断正在执行的任务;
shutdown表示线程池中的任务全部执行完成后再关闭线程池
示例代码
使用CompletionService维护结果
System.out.println("start...");
long begin = System.currentTimeMillis();
// 创建一个线程池
int taskSize = 5;
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<String>(pool);
try {
for (int i = 1; i <= taskSize; i++) {
MyCallable callable = new MyCallable(new Integer(i));
completionService.submit(callable);
}
pool.shutdown();
for (int i = 1; i <= taskSize; i++) {
System.out.println(completionService.take().get());
}
} catch (Exception e) {
e.printStackTrace();
}
// 关闭线程池
long end = System.currentTimeMillis();
System.out.println("end..excute time:" + (end - begin) + "ms");
return;
自己创建list维护执行结果
System.out.println("start...");
long begin = System.currentTimeMillis();
ExecutorService pool = null;
try {
int taskSize = 5;
// 创建一个线程池
pool = Executors.newFixedThreadPool(taskSize);
// 创建多个返回值的任务
List<Future> list = Lists.newArrayList();
for (int i = 1; i <= taskSize; i++) {
MyCallable callable = new MyCallable(new Integer(i));
Future<String> future = pool.submit(callable);
list.add(future);
System.out.println("已添加" + i);
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).get().toString());
}
} catch (Exception e) {
e.printStackTrace();
}
// 关闭线程池
pool.shutdown();
long end = System.currentTimeMillis();
System.out.println("end..excute time:" + (end - begin) / 1000 + "s");
return;