前言:
ThreadPoolExecutor 的 void execute(Runnable command) 方法,利用这个方法虽然可以提交任务,但是却没有办法获取任务的执行结果(execute() 方法没有返回值)
而很多场景下,我们又都是需要获取任务的执行结果的。那 ThreadPoolExecutor 是否提供了相关功能呢?必须的,这么重要的功能当然需要提供了——那就是sumbit
execute只能提交Runnable类型的任务,无返回值。submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
execute在执行任务时,如果遇到异常会直接抛出,而submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常。
一、ThreadPoolExecutor的中的submit和FutureTask
Executors 本质上是 ThreadPoolExecutor 类的封装.
Executors类和ThreadPoolExecutor都是util.concurrent并发包下面的类, Executos下面的newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor、newCachedThreadPool底线的实现都是用的ThreadPoolExecutor实现的,所有ThreadPoolExecutor更加灵活。
Java 通过 ThreadPoolExecutor 提供的 3 个 submit() 方法和 1 个 FutureTask 工具类来支持获得任务执行结果的需求。下面我们先来介绍这 3 个 submit() 方法,这 3 个方法的方法签名如下。
// 提交 Runnable 任务 Future<?> submit(Runnable task); // 提交 Callable 任务 <T> Future<T> submit(Callable<T> task); // 提文 Runnable 任务及结果引用 <T> Future<T> submit(Runnable task T result);
你会发现它们的返回值都是 Future 接口
Future 接口有 5 个方法
取消任务的方法 cancel()、判断任务是否已取消的方法 isCancelled()、判断任务是否已结束的方法 isDone()以及2 个获得任务执行结果的 get() 和 get(timeout, unit)
其中最后一个 get(timeout, unit) 支持超时机制。通过 Future 接口的这 5 个方法你会发现,我们提交的任务不但能够获取任务执行结果,还可以取消任务。不过需要注意的是:这两个 get() 方法都是阻塞式的,如果被调用的时候,任务还没有执行完,那么调用 get() 方法的线程会阻塞,直到任务执行完才会被唤醒。
这 3 个 submit() 方法之间的区别在于方法参数不同,下面我们简要介绍一下。
1.提交 Runnable 任务 submit(Runnable task) :这个方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。
2.提交 Callable 任务 submit(Callable<T> task):这个方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果。
3.提交 Runnable 任务及结果引用 submit(Runnable task, T result):这个方法很有意思,假设这个方法返回的 Future 对象是 f,f.get() 的返回值就是传给 submit() 方法的参数 result。这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要你注意的是 Runnable 接口的实现类 Task 声明了一个有参构造函数 Task(Result r) ,创建 Task 对象的时候传入了 result 对象,这样就能在类 Task 的 run() 方法中对 result 进行各种操作了。result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。
那么既然Executor是对ThreadPoolExecutor的封装,那么通过Executor创建的线程池自然也同样有上述3个submit方法和1个FutureTask工具类。
二、通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
package Thread; /** * 用Callable和FutureTask创建线程 */ import java.util.concurrent.*; import java.util.concurrent.ExecutorService; public class CallableFutureTask { public static void main(String[] args) { //第一种方式 ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); executor.submit(futureTask); executor.shutdown(); //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread /*Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); Thread thread = new Thread(futureTask); thread.start();*/ try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("主线程在执行任务"); try { System.out.println("task运行结果"+futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("所有任务执行完毕"); } } 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 += i; return sum; } }
Runnable任务类型和Callable任务类型
Runnable接口、Callable接口创建线程
首先我们要知道可以通过以下两种创建线程
实现Runable接口,重写run方法。
使用Callable接口 和 Future接口创建线程。(线程处于并发状态, 默认异步执行)
实现Runable接口,重写run方法。
package Thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程创建——》实现Runnable接口,重写run方法 */ class MyRunable implements Runnable { @Override public void run() { System.out.println("实现Runable接口,重写run方法"); } } public class thread3 { // 使用了线程池 public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); MyRunable myRunable = new MyRunable(); es.submit(myRunable); // 将我们的Runnable任务提交到我们线程池中 es.shutdown(); // FutureTask:是对Runnable和Callable的进一步封装, // 相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多 } // 未使用线程池(只是Thread) public static void main1(String[] args) { MyRunable myRunable = new MyRunable(); Thread thread = new Thread(myRunable); // Thread thread1 = new Thread(new MyRunable()); thread.start(); } }
使用Callable和Future创建线程
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。
public interface Callable<V> { V call() throws Exception; }
Java5提供了Future接口来接收Callable接口中call()方法的返回值。
Callable接口是 Java5 新增的接口,不是Runnable接口的子接口,所以Callable对象不能直接作为Thread对象的target。
针对这个问题,引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口,可以作为Thread对象的target 。同时,Java5提供了一个RunnableFuture接口的实现类:FutureTask ,FutureTask可以作为 Thread对象的target
一个案例
package Thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; /** * 和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。 * 同时创建对象的时候, * 》call()方法可以有返回值 * * 》call()方法可以声明抛出异常 * Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask, * 这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。 */ class MyCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("这是用Callable创建线程的一个尝试!"); return "xixi"; } } public class thread1 { // 只是用来Thread public static void main1(String[] args) throws ExecutionException, InterruptedException { // 1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。 MyCallable myThread = new MyCallable(); // myThread是一个Callable对象 //2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值 FutureTask<String> futureTask = new FutureTask<String>(myThread); // 与Callable关联 //3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)——实质上还是以Callable对象来创建并启动线程 // FutureTask实现Future接口,说明可以从FutureTask中通过get取到任务的返回结果,也可以取消任务执行(通过interreput中断) Thread thread = new Thread(futureTask, "有返回值的线程"); thread.start(); // 4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值 System.out.println("子线程的返回值" + futureTask.get()); //get()方法会阻塞,直到子线程执行结束才返回 } // 使用了线程池 public static void main2(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); MyCallable myCallable = new MyCallable(); es.submit(myCallable); // 你直接把Callable任务丢给线程池,获取不到call返回值 es.shutdown(); // FutureTask:是对Runnable和Callable的进一步封装, //相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多 } // 使用了线程池 public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService es = Executors.newCachedThreadPool(); MyCallable myCallable = new MyCallable(); //2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值 FutureTask<String> futureTask = new FutureTask<String>(myCallable); // 与Callable关联 es.submit(futureTask); // 你直接把Callable任务丢给线程池,获取不到call返回值 System.out.println(futureTask.get()); // 通过futureTask打印返回值 es.shutdown(); } }
使用Callable和Future创建线程的 总结
使用Callable和Future创建线程的步骤如下:(未使用线程池)
(1)定义一个类实现Callable接口,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象
(3)使用FutureTask对象作为Thread对象的target创建并启动线程
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
使用Callable和Future创建线程的步骤如下:(使用线程池)
(1)定义一个类实现Callable接口,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
(3)创建线程池
(4)通过sumbit()把封装了Callable对象的futureTask提交到线程池中
(5)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
当然如果你用了线程池,你也可以直接提交把你实例化的Callable对象和Runnable对象提交线程池中(不用FutureTask封装)
但是这里,你直接把Callable任务丢给线程池,你获取不到call方法返回值
// FutureTask:是对Runnable和Callable的进一步封装,
//相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多
Runable和Callable任务类型的区别:
两者都可以被ExecutorService执行
Callable的call()方法只能通过ExecutorService的 submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future.
Runnable的run方法,无返回值,无法抛出经过检查的异常。Callable的call方法,有返回值V,并且可能抛出异常。
将Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,
并且会返回执行结果Future对象。
将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,
并且会返回执行结果Future对象,但是在该Future对象上调用的方法返回的是null.
线程池的一些补充
Runnable:
可以直接用execute或sumbit提交到线程池
Callable:
功能相比Runnable来说少很多,不能用来创建线程(要和Future接口一起),也不能直接扔给线程池的execute方法。但是其中的call方法有返回值
FutureTask 实现了 Runnable 和 Future 接口,由于实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果
三、总结
线程的创建
线程的创建有4种方法
1、通过继承Thread类,重写其中的run方法。
2、通过实现Runnable接口,重写其中的run方法。
我们的Runnable任务可以直接作为new Thread中的target。
3、通过实现Callable类,重写其中的call方法
但是此时你得到的MyCallable实例(callable任务)不能直接作为new Thread()中的target,放到括号中。你需要通过FutureTask包装一下你的MyCallable,得到futureTask因为FutureTask实现了Runnable接口,所以futureTask可以作为Thread类的Target——》new Thread(futureTask)
上面我们说的是没有用到线程池的情况下。
如果使用了线程池,线程池的sumbit可以提交Runnable任务和Callable任务。但是execute只能提交Runnable任务。
// FutureTask:是对Runnable和Callable的进一步封装,
// 相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多
Runnable和Callable
1.实现Runnable/Callable接口相比继承Thread类的优势
(1)适合多个线程进行资源共享
(2)可以避免java中单继承的限制
(3)增加程序的健壮性,代码和数据独立
(4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类
2.Callable和Runnable的区别
(1) Callable重写的是call()方法,Runnable重写的方法是run()方法
(2) call()方法执行后可以有返回值,run()方法没有返回值
(3) call()方法可以抛出异常,run()方法不可以
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果