Future和Callable介绍
线程Thread默认情况下不具有返回值的功能,如果在需要取得返回值的情况下是极为不方便的,在Java5的并发包中可以使用Future和Callable来实现线程具有返回值的功能。
Future 中文翻译为将来、未来,Callable 中文翻译为可随时支取的、请求即付的、随时可偿还的。
Callable和Runnable的区别
接口Callable与线程功能联系很紧密,和Runnable主要区别为:
1)Callable接口中的call() 方法可以有返回值,Runnable接口中run()方法没有返回值
2)Callable接口中的call()方法可以声明抛出异常,Runnable接口中的run()方法不可以声明抛出异常。
线程池中执行Callable接口中任务后,可以使用Future接口来获取返回值。
Future和Callable的使用
ExecutorService中的submit(Callable)方法可以执行Callable任务,并且使用Future接口中的get()方法获取返回值。示例如下:
创建MyCallable对象,使其实现Callable接口
package com.futurecallable;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class MyCallable implements Callable<String> {
private int age;
public MyCallable(int age) {
this.age = age;
}
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(3);
return "the age is : " + this.age;
}
}
创建Run类来执行Callable任务
package com.futurecallable;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Run {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable(40);
// 创建线程池,用于执行Callable任务
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 5L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
Future<String> future = executor.submit(myCallable);
System.out.println("start : " + System.currentTimeMillis());
try {
// 使用get()获取任务执行结果
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("end : " + System.currentTimeMillis());
}
}
执行结果:
start : 1507965180517
the age is : 40
end : 1507965183523
说明,从控制台打印的结果看,get()具有阻塞性的。
Future和Runnable的使用
方法submit()不仅可以传入Callable对象,还可以传入Runnable对象,说明submit()方法支持有返回值和无返回值的功能。
无返回值的示例
package com.futurecallable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Run {
public static void main(String[] args) {
try {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("do sth");
}
};
ExecutorService service = Executors.newCachedThreadPool();
Future future = service.submit(runnable);
System.out.println(future.get() + "----" + future.isDone());
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
do sth
null----true
说明,submit()方法传入Runnable对象时,future获取任务返回值方法get()则为null,方法get具有阻塞性,但是isDone()则没有。
有返回值的示例
其实我们利用submit(Runnable, T)这个方法中的第二个参数T作为任务执行结果的返回值,而不使用get()来进行获得。看一下示例
创建一个Student类,用作返回的对象
package com.futurecallable;
public class Student {
private String stuid;
private String stuname;
private String stuage;
public Student() {
super();
}
public String getStuid() {
return stuid;
}
public void setStuid(String stuid) {
this.stuid = stuid;
}
public String getStuname() {
return stuname;
}
public void setStuname(String stuname) {
this.stuname = stuname;
}
public String getStuage() {
return stuage;
}
public void setStuage(String stuage) {
this.stuage = stuage;
}
@Override
public String toString() {
return "Student{" +
"stuid='" + stuid + '\'' +
", stuname='" + stuname + '\'' +
", stuage='" + stuage + '\'' +
'}';
}
}
创建MyRunnable类来执行任务
package com.futurecallable;
public class MyRunnable implements Runnable {
private Student student;
public MyRunnable(Student student) {
this.student = student;
}
@Override
public void run() {
student.setStuid("stu_01");
student.setStuname("Jack");
student.setStuage("30");
}
}
执行任务
package com.futurecallable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyRunnableTest {
public static void main(String[] args) {
try {
Student student = new Student();
MyRunnable myRunnable = new MyRunnable(student);
ExecutorService service = Executors.newCachedThreadPool();
Future<Student> future = service.submit(myRunnable, student);
System.out.println("begin:" + System.currentTimeMillis());
System.out.println(future.get());
System.out.println("end:" + System.currentTimeMillis());
System.out.println("result:" + student);
service.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
begin:1507966486776
Student{stuid='stu_01', stuname='Jack', stuage='30'}
end:1507966486776
result:Student{stuid='stu_01', stuname='Jack', stuage='30'}
从结果上看出,也可以使用此方法来获取任务的返回值。
Future的缺点
接口Future的实现类之一是FutureTask.java,在使用线程池时,默认情况下也是使用FutureTask.java类作为接口Future的实现来。我们知道Callable相较于Runnable是有优势的,但是Future接口中get()方法在获取结果时是阻塞的,如果任务执行时间长,则get()方法将一直等待直到任务执行完毕,如果是这样,前面的执行任务一旦耗时很长,则后面的任务调用get()方法就得等待阻塞,大大降低了运行效率,也就是主线程无法保证先执行的任务先完成任务并返回值,这就是Future的缺点,影响效率。
我们通过下个示例来说明这种情况,并且Java5中提供了解决办法,我们后续再讲解。
创建MyCallable类,用于执行任务
package com.futurecallable;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class MyCallable implements Callable<String> {
private String username;
private long sleepTime;
public MyCallable(String username, long sleepTime) {
this.username = username;
this.sleepTime = sleepTime;
}
@Override
public String call() throws Exception {
System.out.println("username:" + username);
TimeUnit.SECONDS.sleep(sleepTime);
return "the age is : " + this.username;
}
}
执行任务
package com.futurecallable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MyCallableTest {
public static void main(String[] args) {
try {
MyCallable myCallable1 = new MyCallable("username1", 5);
MyCallable myCallable2 = new MyCallable("username2", 4);
MyCallable myCallable3 = new MyCallable("username3", 3);
MyCallable myCallable4 = new MyCallable("username4", 2);
MyCallable myCallable5 = new MyCallable("username5", 1);
List<Callable> callableList = new ArrayList<>();
callableList.add(myCallable1);
callableList.add(myCallable2);
callableList.add(myCallable3);
callableList.add(myCallable4);
callableList.add(myCallable5);
List<Future> futures = new ArrayList<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 5; i++) {
futures.add(executor.submit(callableList.get(i)));
}
System.out.println("first time:" + System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println(futures.get(i).get() + "=====" + System.currentTimeMillis());
}
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
username:username1
username:username5
first time:1507967802840
username:username4
username:username2
username:username3
the result is : username1=====1507967807845
the result is : username2=====1507967807846
the result is : username3=====1507967807846
the result is : username4=====1507967807846
the result is : username5=====1507967807846
从结果上看first time:1507967802840与the result is : username1=====1507967807845时间上相差5s,而其他sleep时间均小于5s,所以在第一个执行完时,其他的线程早执行完,但是由于get的阻塞性,导致后面的线程执行完但是必须等前面的返回后才能返回结果值,这就是Future的缺点。
CompletionService的使用
Future和Callable的使用中,发现FutureTask也有自身的缺点,就是get()方法的阻塞性,为了解决这个问题,Java5中提供了CompletionService接口来解决此问题。
CompletionService的逻辑实现
接口CompletionService的实现是以异步的方式一边生产新的任务,一边处理已完成任务的结果,这是将执行任务和处理任务分开进行处理。使用take取得已完成任务的结果,并按照任务完成的时间顺序处理他们的结果。
CompletionService解决Future的缺点
修改MyCallableTest类如下
package com.futurecallable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MyCallableTest {
public static void main(String[] args) {
try {
MyCallable myCallable1 = new MyCallable("username1", 5);
MyCallable myCallable2 = new MyCallable("username2", 4);
MyCallable myCallable3 = new MyCallable("username3", 3);
MyCallable myCallable4 = new MyCallable("username4", 2);
MyCallable myCallable5 = new MyCallable("username5", 1);
List<Callable> callableList = new ArrayList<>();
callableList.add(myCallable1);
callableList.add(myCallable2);
callableList.add(myCallable3);
callableList.add(myCallable4);
callableList.add(myCallable5);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
// 使用CompletionService类执行任务
CompletionService completionService = new ExecutorCompletionService(executor);
for (int i = 0; i < 5; i++) {
completionService.submit(callableList.get(i));
}
System.out.println("first time:" + System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println("waiting print " + (i + 1) + " result");
System.out.println(completionService.take().get());
}
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
username:username1
username:username2
username:username3
username:username4
username:username5
first time:1507968871720
waiting print 1 result
the result is : username5
waiting print 2 result
the result is : username4
waiting print 3 result
the result is : username3
waiting print 4 result
the result is : username2
waiting print 5 result
the result is : username1
从结果上看,已经解决了Future的阻塞性,就是使用CompletionService接口,那个任务先执行完就先打印这个任务的返回值。
CompletionService的阻塞性
当CompletionService接口中如果没有任务被执行完,则completionService.take().get()方法还是呈现阻塞性。
再修改以上代码如下
package com.futurecallable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MyCallableTest {
public static void main(String[] args) {
try {
MyCallable myCallable1 = new MyCallable("username1", 5);
MyCallable myCallable2 = new MyCallable("username2", 4);
MyCallable myCallable3 = new MyCallable("username3", 3);
MyCallable myCallable4 = new MyCallable("username4", 2);
MyCallable myCallable5 = new MyCallable("username5", 1);
List<Callable> callableList = new ArrayList<>();
callableList.add(myCallable1);
callableList.add(myCallable2);
callableList.add(myCallable3);
callableList.add(myCallable4);
callableList.add(myCallable5);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
// 使用CompletionService类执行任务
CompletionService completionService = new ExecutorCompletionService(executor);
for (int i = 0; i < 5; i++) {
completionService.submit(callableList.get(i));
}
System.out.println("first time:" + System.currentTimeMillis());
// 将循环数设置大于实际任务数,则第6个completionService.take().get()方法则会等待任务执行完,但是已经没有任务在执行,所以程序会在此阻塞。
for (int i = 0; i < 6; i++) {
System.out.println("waiting print " + (i + 1) + " result");
System.out.println(completionService.take().get());
}
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
从图上可看到,程序并没有执行结束,而是阻塞在此,所以我们使用此接口时要确定对应的任务数。
CompletionService接口中take()方法
方法take()获取的是最先完成任务的Future对象,就是谁执行时间最短谁最先返回。
我们还是通过示例来说明
package com.futurecallable;
import java.util.concurrent.*;
public class Run {
public static void main(String[] args) {
try {
ExecutorService service = Executors.newCachedThreadPool();
CompletionService completionService = new ExecutorCompletionService(service);
for (int i = 0; i < 10; i++) {
completionService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
long sleep = (int) (Math.random() * 1000);
System.out.println("sleep = " + sleep + " " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(sleep);
return "hongguo:" + sleep + " " + Thread.currentThread().getName();
}
});
}
for (int i = 0; i < 10; i++) {
System.out.println(completionService.take().get());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
sleep = 512 pool-1-thread-1 1507969840237
sleep = 284 pool-1-thread-3 1507969840238
sleep = 311 pool-1-thread-2 1507969840238
sleep = 652 pool-1-thread-5 1507969840238
sleep = 394 pool-1-thread-4 1507969840238
sleep = 783 pool-1-thread-7 1507969840239
sleep = 231 pool-1-thread-6 1507969840239
sleep = 449 pool-1-thread-8 1507969840239
sleep = 180 pool-1-thread-9 1507969840239
sleep = 132 pool-1-thread-10 1507969840240
hongguo:132 pool-1-thread-10
hongguo:180 pool-1-thread-9
hongguo:231 pool-1-thread-6
hongguo:284 pool-1-thread-3
hongguo:311 pool-1-thread-2
hongguo:394 pool-1-thread-4
hongguo:449 pool-1-thread-8
hongguo:512 pool-1-thread-1
hongguo:652 pool-1-thread-5
hongguo:783 pool-1-thread-7
从结果上看,sleep的值越小,越先执行完并返回。
CompletionService与Runnable的组合
同样都是使用submit()方法,所以我们也可以使用CompletionService来实现Runnable接口的返回值。
我们修改MyRunnableTest类如下
package com.futurecallable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyRunnableTest {
public static void main(String[] args) {
try {
Student student = new Student();
MyRunnable myRunnable = new MyRunnable(student);
Executor executor = Executors.newCachedThreadPool();
CompletionService service = new ExecutorCompletionService(executor);
Future<Student> future = service.submit(myRunnable, student);
System.out.println("begin:" + System.currentTimeMillis());
System.out.println(future.get());
System.out.println("end:" + System.currentTimeMillis());
System.out.println("result:" + student);
service.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果与之前的类一样。
总结
Future和Callable解决了多线程中返回值的问题,但是其也是有缺点的就是get()是阻塞性地,为了解决这个问题,我们又引入了CompletionService接口,此接口完全避免了FutureTask的阻塞的缺点,可更加有效的处理Future的返回值,就是先执行完的任务,就先返回结果值。