有返回值的多线程:Callable与Future、FutureTask
Callable
Callable是一个泛型接口,只实现了一个call()方法,这个跟Runnable类似,但是call()方法返回了一个传入的泛型结果,并且该方法是会抛出异常的
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
创建个实现类,传入了Integer类型,那么在使用这个实现类创建多线程时,获取到的返回结果也将是个Integer类型的数据。
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return null;
}
}
接下来看一下如何使用Callable来创建多线程,这里是直接创建并启动线程,通过线程池创建Callable线程点区别,可以移步
public class CallableDemo implements Callable<Integer> {
public static void main(String[] args) {
// 1.实现Callable类,实例化
CallableDemo demo = new CallableDemo();
// 2.1 使用FutureTask类对其进行包装
FutureTask<Integer> futureTask = new FutureTask<>(demo);
// 2.2 创建一个线程,并传入包装后的 futureTask
Thread thread = new Thread(futureTask);
// 2.3 启动线程
thread.start();
// 3.3 调用FutureTask类的get()方法获取线程执行的返回结果
int result = 0;
try {
result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("end");
}
@Override
public Integer call() throws Exception {
int num = 0;
while (5 > num){
Thread.sleep(2000);
num++;
System.out.println("Num is " + num);
}
return num;
}
}
//执行结果
Num is 1
Num is 2
Num is 3
Num is 4
Num is 5
5
end
通过上面的代码,我们会发现两点:
- FutureTask 类对Callable的实现类进行了包装,那这个FutureTask又是什么
- 结合执行结果中,明显看到主线程中打印的数值5和end都是在子线程执行完毕之后,说明FutureTask的get()方法是会阻塞主线程的
Future、FutureTask
带着前面的问题,我们来看一下Future接口和其唯一实现类FutureTask
Future接口
public interface Future<V> {
// 尝试取消线程
boolean cancel(boolean mayInterruptIfRunning);
// 获取线程是否已取消
boolean isCancelled();
// 获取线程是否已执行完成
boolean isDone();
// 获取线程执行结果
V get() throws InterruptedException, ExecutionException;
// 获取线程执行结果,在timout时间后不再阻塞
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future接口中定义了5个方法,每个方法的作用注释中说明了,get()方法的使用在上文的代码有示例
现在需要注意cancel()方法,需要传一个布尔参数:
- 传入false时,只取消已经提交但是还未被运行的线程
- 传入true时,取消所有已提交的线程
我们来实际使用一下这个方法
public class CallableDemo implements Callable<Integer> {
public static void main(String[] args) {
CallableDemo demo = new CallableDemo();
FutureTask<Integer> futureTask = new FutureTask<>(demo);
Thread thread = new Thread(futureTask);
thread.start();
int result = 0;
// 启动另外一个线程,3s后异步中断上述子线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
futureTask.cancel(true);
System.out.println("Canceled futureTask.");
}
});
thread1.start();
try {
result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("end");
}
@Override
public Integer call() throws Exception {
int num = 0;
while (5 > num){
Thread.sleep(1000);
System.out.println(num);
num++;
}
return 1;
}
}
看一下执行结果,子线程中断,并抛出了异常,与预期的一样
接下来,修改call()方法为下面的代码,主要区别是去掉了Thread.sleep()的调用,通过一个大数值条件保证执行时间
@Override
public Integer call() throws Exception {
long num = 1000000000;
for (long i = 2; i < num; i++) {
if(num % i == 0){
System.out.println(0);
}
}
return 1;
}
然后将触发子线程的cancel()方法的另一个子线程中的延迟时间改为500ms,于是得到了下面的运行结果
结果发现,在调用了cancel()方法并且主线程抛出异常后,子线程仍然运行直至结束,这是为什么呢?我们来看一下FutureTask中cancel()方法的源码
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
//【这里发出了中断指令】
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
原来cancel()方法的原理就是调用正在运行的线程的interrupt()方法即发送中断指令。Thread.sleep()方法是能够对中断指令做出相应的,所以在第一次的测试符合预期,但是当第二次测试时,并没有对Thread的interrupt()方法做出中断响应,那子线程肯定就会继续执行下去。因此,我们可以得出一个结论:当一个线程支持被取消时,就应该能够对interrupt()方法做出正确的响应。
那我们再次更改一下call()方法的代码
@Override
public Integer call() throws Exception {
long num = 1000000000;
for (long i = 2; i < num; i++) {
if(Thread.currentThread().isInterrupted()){
// 当线程被中断后,跳出循环
System.out.println("Interrupted.");
break;
}else{
if(num % i == 0){
System.out.println(num % i);
}
}
}
return 1;
}
得到了下面的运行结果,结果看来符合预期,在对中断指令做出相应之后