2.3 第三种:实现 Callable 接口
Callable接口详解_逍遥绝情的博客-CSDN博客_callable接口(不懂看这个博客)
Callable两种执行方式:第一种借助线程池来运行
- 实现 Callable 接口,需要返回值类型
- 重写 call 方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService = Executor.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(1);
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow():
Future接口
Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、
等待完成和得到计算的结果。
当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
一旦计算完成了,那么这个计算就不能被取消。
package com.example.democrud.democurd.test01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; //Callable<Boolean> 布尔类型和call 同步 //callable的好处: //1.可以定义返回值 //2.可以抛出异常 public class testThred02 implements Callable<Boolean> { public String url;//图片路径 public String name;//图片的名字 public testThred02(String url, String name) { this.name = name; this.url = url; } @Override //代表重写 public Boolean call() { downloadpng down = new downloadpng(); down.download(url, name); System.out.println("下载的文件名为" + name); return true; } //主线程 main方法 public static void main(String[] args) throws ExecutionException, InterruptedException { testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg"); testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg"); testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg"); // 创建执行服务 线程池中有3个线程 ExecutorService ser = Executors.newFixedThreadPool(3); // 提交执行 执行每个线程 Future<Boolean> result1 = ser.submit(thred01); Future<Boolean> result2 = ser.submit(thred02); Future<Boolean> result3 = ser.submit(thred03); // 获取结果: boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); //关闭服务 ser.shutdownNow(); } //先写一个下载器 class downloadpng { //引用地址和名字进行下载 public void download(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader 方法出现问题"); } } } }
Callable两种执行方式:第二种借助FutureTask执行
示例代码中,CallableTest 类实现了 Callable 接口的 call() 方法。在 main() 函数内首先创建了一个 FutureTask 对象(构造函数为 CallableTest 实例),然后使用创建的 FutureTask 对象作为任务创建了一个线程并且启动它,最后通过 futureTask.get() 等待任务执行完毕返回结果。
Future接口 Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、 等待完成和得到计算的结果。 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。 一旦计算完成了,那么这个计算就不能被取消。 FutureTask类 FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。 FutureTask有2个构造方法:入参分别是Callable或者Runnbale对象。 FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
public class FutureTask<V> implements RunnableFuture<V>{} public interface RunnableFuture<V> extends Runnable, Future<V>
代码示例:
package com.sjmp.practice; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableTest implements Callable<String> { @Override public String call() throws Exception { return "Hello CallableThread"; } public static void main(String[] args) { CallableTest callableTest = new CallableTest(); FutureTask<String> futureTask = new FutureTask<>(callableTest); new Thread(futureTask).start(); try { String s = futureTask.get(); System.out.println(s); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
Hello CallableThread Process finished with exit code 0
Runnable和Callable的区别(重要)
相同点 1、两者都是接口;(废话) 2、两者都可用来编写多线程程序; 3、两者都需要调用Thread.start()启动线程; 不同点 1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果; 而实现Runnable接口的任务线程不能返回结果; 2、Callable接口的call()方法允许抛出异常; 而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛; 注意点 Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
扩展(重要):
Callable接口详解
- Callable: 返回结果并且可能抛出异常的任务。
- 优点:
- 可以获得任务执行返回值;
- 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。
Runnable和Callable的区别:
- 1、Callable规定的方法是call(),Runnable规定的方法是run().
- 2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
- 3、call方法可以抛出异常,run方法不可以
- 4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 5、代码示例:
//Callable 接口 public interface Callable<V> { V call() throws Exception; } // Runnable 接口 public interface Runnable { public abstract void run(); }
Future接口
- Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
- 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
- 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
- 一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
- FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
- FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。
Callable两种执行方式
- 1、借助FutureTask执行
- FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
- 具体流程:
//定义实现Callable接口的的实现类重写call方法。 public class MyCallableTask implements Callable<Integer>{ @Override public Integer call() throws Exception { //TODO 线程执行方法 } } --------------------------------------------------------- //创建Callable对象 Callable<Integer> mycallabletask = new MyCallableTask(); //开始线程 FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask); new Thread(futuretask).start(); -------------------------------------------------------- 通过futuretask可以得到MyCallableTask的call()的运行结果: futuretask.get();
- 2、借助线程池来运行
- 线程池中执行Callable任务原型:
public interface ExecutorService extends Executor { //提交一个Callable任务,返回值为一个Future类型 <T> Future<T> submit(Callable<T> task); //other methods... }
- 借助线程池来运行Callable任务的一般流程为:
ExecutorService exec = Executors.newCachedThreadPool(); Future<Integer> future = exec.submit(new MyCallableTask());
- 通过future可以得到MyCallableTask的call()的运行结果: future.get();
举例说明
- 例1:
public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{ //创建一个线程池 ExecutorService executor = Executors.newCachedThreadPool(); Future<String> future = executor.submit(()-> { TimeUnit.SECONDS.sleep(5); return "CallableTest"; }); System.out.println(future.get()); executor.shutdown(); } }
- 例2:Callable任务借助FutureTask运行:
public class CallableAndFutureTask { Random random = new Random(); public static void main(String[] args) { Callable<Integer> callable = new Callable<Integer>() { public Integer call() throws Exception { return random.nextInt(10000); } }; FutureTask<Integer> future = new FutureTask<Integer>(callable); Thread thread = new Thread(future); thread.start(); try { Thread.sleep(2000); System.out.println(future.get()); } catch (Exception e) { e.printStackTrace(); } } }
- 例3:Callable任务和线程池一起使用,然后返回值是Future:
public class CallableAndFuture { Random random = new Random(); public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); Future<Integer> future = threadPool.submit(new Callable<Integer>() { public Integer call() throws Exception { return random.nextInt(10000); } }); try { Thread.sleep(3000); System.out.println(future.get()); } catch (Exception e) { e.printStackTrace(); } } }
- 例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合:
class MyCallableTask implements Callable<String> { private int id; public OneTask(int id){ this.id = id; } @Override public String call() throws Exception { for(int i = 0;i<5;i++){ System.out.println("Thread"+ id); Thread.sleep(1000); } return "Result of callable: "+id; } } public class Test { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for (int i = 0; i < 5; i++) { results.add(exec.submit(new MyCallableTask(i))); } for (Future<String> fs : results) { if (fs.isDone()) { try { System.out.println(fs.get()); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("MyCallableTask任务未完成!"); } } exec.shutdown(); } }
StopWatch的使用
- Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。
public String call() throws Exception { StopWatch stopWatch = new StopWatch(); stopWatch.start("测试StopWatch"); //TODO 业务逻辑 stopWatch.stop(); return "test"; }
2.4 Lamda 表达式
Lamda 表达式属于函数式编程的概念
- 理解 Functional Interface(函数式接口)是学习 Java8 Lambda 表达式的关键所在。
- 函数式接口:说起Lambda,就必须了解函数式接口,因为要使用Lambda,必须在函数式接口上使用。函数式接口:就是一个有且仅有一个抽象方法,但是可以有多个默认方法的接口,这样的接口可以隐式转换为Lambda表达式。一般在函数式接口上都有个注解@FunctionalInterface,该注解的作用类似@Override一样告诉编译器这是一个函数式接口,用于编译期间检测该接口是否仅有一个抽象方法,如果拥有多个则编译不通过。
Lamda 表达式的演进:
package com.example.democrud.democurd.test01; public class testLambda { //3.静态内部类 static class like2 implements iLike{ @Override public void lamdba() { System.out.println("我在学习lamdba-2"); } } public static void main(String[] args) { iLike like = new like(); like.lamdba(); like = new like2(); like.lamdba(); //4.局部内部类 class like3 implements iLike{ @Override public void lamdba() { System.out.println("我在学习lamdba-3"); } } like=new like3(); like.lamdba(); //5.匿名内部类 没有类的名称必须借助接口或者父类 like=new iLike() { @Override public void lamdba() { System.out.println("我在学习lamdba-4"); } }; like.lamdba(); //6.lambda简化 like=()->{ System.out.println("我在学习lamdba-5"); }; like.lamdba(); } } //1.定义一个函数式接口 interface iLike{ void lamdba(); } //2.实现类 class like implements iLike{ @Override public void lamdba() { System.out.println("我在学习lamdba-1"); } }
DEMO:
package com.example.democrud.democurd.test01; public class testLamdba01 { //静态内部类 static class Love1 implements iLove { @Override public void love(int a) { System.out.println("打印我输入的数字是" + a); } } public static void main(String[] args) { iLove love = new Love(); love.love(20); love = new Love1(); love.love(12); //匿名类 love = new iLove() { @Override public void love(int a) { System.out.println("打印我输入的数字是" + a); } }; love.love(33); //lambba简化 love = (int a) -> { System.out.println("打印我输入的数字是" + a); }; love.love(56); //简化参数类型 love = (a) -> { System.out.println("打印我输入的数字是" + a); }; love.love(57); //简化中括号 love = a -> { System.out.println("打印我输入的数字是" + a); }; love.love(58); //简化花括号 love = a -> System.out.println("打印我输入的数字是" + a); love.love(59); //总结:lamdba表达式只能有代码一行的情况下 才能简化一行,多行用大括号包围 //用户函数是接口 //函数是接口:任何接口如果只包含唯一一个 抽象方法那么他就是一个函数式接口 } } interface iLove { void love(int a); } //实现类 class Love implements iLove { @Override public void love(int a) { System.out.println("打印我输入的数字是" + a); } }