当前位置: 代码迷 >> 综合 >> 有返回值的多线程:Callable与Future、FutureTask
  详细解决方案

有返回值的多线程:Callable与Future、FutureTask

热度:54   发布时间:2024-02-11 22:19:37.0

有返回值的多线程: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> {@Overridepublic 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 创建一个线程,并传入包装后的 futureTaskThread 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");}@Overridepublic 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() {@Overridepublic 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");}@Overridepublic 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 exceptionif (mayInterruptIfRunning) {try {Thread t = runner;if (t != null)//【这里发出了中断指令】t.interrupt();} finally { // final stateUNSAFE.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;
}

得到了下面的运行结果,结果看来符合预期,在对中断指令做出相应之后

在这里插入图片描述

  相关解决方案