Future和Callable--治理线程第二大法宝

it2024-07-02  40

Runnable的缺陷

有两大缺陷; 1.不能返回一个返回值,返回值为void

2.也不能抛出checked Exception 在run()方法中,无法抛出异常,只能使用try catch 来处理异常;下面的那个对比方法是可以抛出异常的;

为什么又这样的缺陷? 因为它设计的时候,方法返回值为void 而且也设计成无法抛出异常; Runnable 为什么设计成这样? 如果抛出异常后,就给Thread 这时候没办法处理;所以,就设计成为直接在Runnable中进行处理; 针对于无法抛出检查后异常这个缺陷的补救措施就是Callable接口

Callable接口 源码如下: 可以看到第一它又返回值,返回值是泛型;第二,它可以抛出异常; Future类 Future作用:核心思想,一个方法它的计算可能会很耗时,那么它就会让子线程去执行,然后再去干自己的事情,等他想用这个返回值的时候,再去看他有没有计算出来;如果计算出结果了,就去获取这个值; Future和Callable的关系 Future的主要方法 有五个 取消的方法 完毕不一定是成功执行,失败了,执行完毕也会返回true; 代码演示:

package future; import java.util.Random; import java.util.concurrent.*; /** * 描述 : 演示一个Future的使用方法 */ public class OneFuture { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); Future<Integer> future = service.submit(new CallableTask()); try { // 通过get来获取值,这里需要等待大概3秒钟时间 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } service.shutdown(); } static class CallableTask implements Callable<Integer>{ @Override public Integer call() throws Exception { Thread.sleep(3000); return new Random().nextInt(1000); } } }

结果会是一个随机数;

下面进行lambda表达式,会方便一些;

package future; import java.util.Random; import java.util.concurrent.*; /** * 描述 : 演示一个Future的使用方法,lambda表达式 */ public class OneFuture { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); Future<Integer> future = service.submit(new Callable() { @Override public Integer call() throws Exception { Thread.sleep(3000); return new Random().nextInt(1000); } }); try { // 通过get来获取值,这里需要等待大概3秒钟时间 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } service.shutdown(); } }

多个任务,用Future数组来获取结果;

package future; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.*; /** * 描述: 演示批量提交任务时,用List来批量接收结果; */ public class MultiFutures { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(2); ArrayList<Future> futures = new ArrayList<>(); // 提交任务,向数组中提交20个future for (int i = 0; i < 20; i++) { Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { Thread.sleep(3000); return new Random().nextInt(1000); } }); futures.add(future); } // 提交完任务以后,下面的任务还会继续执行 for (int i = 0; i < 20; i++) { Future<Integer> future = futures.get(i); try { // 注意程序会执行到这里,当这里获取不到的时候,才会进行阻塞 Integer integer = future.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }

结果是,没3秒中,会打印两个 任务执行过程中抛出Exception和isDone展示:

package future; import java.util.concurrent.*; /** * 描述: 演示get方法过程中抛出异常,for循环为了演示抛出Exception的时机:并不是说一产生异常就抛出, * 直到我们get执行时,才会抛出 */ public class GetException { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(20); Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { throw new IllegalAccessException("Callable抛出异常"); } }); try { future.get(); } catch (InterruptedException e) { System.out.println("抛出了InterruptedException异常"); e.printStackTrace(); } catch (ExecutionException e) { System.out.println("抛出了ExecutionException异常"); e.printStackTrace(); } } }

结论如下: 这里虽然抛出的IllegalAccessException(“Callable抛出异常”),但是,其抛出的都是ExecutionException。 对其升级:

package future; import java.util.concurrent.*; /** * 描述: 演示get方法过程中抛出异常,for循环为了演示抛出Exception的时机:并不是说一产生异常就抛出, * 直到我们get执行时,才会抛出 */ public class GetException { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(20); Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { throw new IllegalAccessException("Callable抛出异常"); } }); try { // 其实任务一在线程池中,它就已经是存在了,但是你不会调用get,它是不会抛出的 for (int i = 0; i < 5; i++) { System.out.println(i); Thread.sleep(500); } future.get(); } catch (InterruptedException e) { System.out.println("抛出了InterruptedException异常"); e.printStackTrace(); } catch (ExecutionException e) { System.out.println("抛出了ExecutionException异常"); e.printStackTrace(); } } }

可以证明,只有在调用get的时候,才会抛出异常; isDone()方法测试 获取任务超时学习:

package future; import java.util.concurrent.*; /** * 描述: 演示get的超时方法,需要注意超时后,需要处理,调用future.cancel(); * 演示cancel传入true和false的区别, * 代表是否中断正在执行的任务。 */ public class Timeout { private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告"); private static final ExecutorService exec = Executors.newFixedThreadPool(10); static class Ad{ String name; public Ad(String name){ this.name = name; } @Override public String toString(){ return "Ad{" + "name =" + name + "}"; } } static class FetchAdTask implements Callable<Ad> { @Override public Ad call() throws Exception { try { Thread.sleep(3000); }catch (InterruptedException e){ System.out.println("sleep期间被中断了"); return new Ad("被中断时候的默认广告-1"); } return new Ad("有网络的广告"); } } public void printAd(){ Future<Ad> f = exec.submit(new FetchAdTask()); Ad ad; try { ad = f.get(4000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { ad = new Ad("被中断时候的默认广告"); e.printStackTrace(); } catch (ExecutionException e) { ad = new Ad("被中断时候的默认广告"); e.printStackTrace(); } catch (TimeoutException e) { ad = new Ad("被中断时候的默认广告"); System.out.println("超时,未获取到广告"); boolean cancel = f.cancel(false); System.out.println("cancel的结果:" + cancel); } exec.shutdown(); System.out.println(ad); } public static void main(String[] args) { Timeout timeout = new Timeout(); timeout.printAd(); } }

首先,他会去执行printAd()方法,然后,就执行上面的函数,f.get()方法会去获取,在4s内,如果有返回值的话,它就会获取到,而FetchAdTask()任务中,过个3s后,会返回一个有网络的广告;

如果,我把这个时间改为2000,那么他就会产生如下结果: 这时候,会放弃结果; 但是,当我们f.cancel()中传入true时,这个时候,当一定时间内没获取到的时候,它就会抛出一个异常; 它会在call()运行的时候,抛出一个异常;就是在Thread.sleep(3000);的过程抛出异常; 如果填入的是ture,那么就给他一个中断信号,如果填入的是false,那么就不会有中断信号,会继续执行下去; 如果我们知道该任务可以处理中断,那么就可以调用cancel(true)来处理; 其他的条件一般用false 它也是可以来避免启动尚未启动的任务的; 用FutureTask来创建Future 以前是使用submit()的返回来获得Future的,现在可以使用FuturnTask来获取Future; 代码演示:

package future; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 描述: 演示FutureTask的用法 */ public class FutureTaskDemo { public static void main(String[] args) { Task task = new Task(); FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(task); // FutureTask继承Runnable接口,所以,可以这么使用; new Thread(integerFutureTask).start(); try { // 使用FutureTask可以把结果进行返回; System.out.println("task运行结果:" + integerFutureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class Task implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("子线程正在计算"); Thread.sleep(3000); int sum = 0; for (int i = 0; i < 100; i++) { sum = sum + 1; } return sum; } }

下面是用线程池来使用;

package future; import java.util.concurrent.*; /** * 描述: 演示FutureTask的用法 */ public class FutureTaskDemo { public static void main(String[] args) { Task task = new Task(); FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(task); /*// FutureTask继承Runnable接口,所以,可以这么使用; new Thread(integerFutureTask).start();*/ ExecutorService service = Executors.newFixedThreadPool(10); // 把它放到线程池中进行执行 service.submit(integerFutureTask); try { // 使用FutureTask可以把结果进行返回; 也可以来进行调用 System.out.println("task运行结果:" + integerFutureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class Task implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("子线程正在计算"); Thread.sleep(3000); int sum = 0; for (int i = 0; i < 100; i++) { sum = sum + 1; } return sum; } }

结果如下: 比如说:你获取的第一个future非常慢,那么你后面的future就会被阻塞;

最新回复(0)