目录
一、ThreadPoolExecutor的中的submit和FutureTask
二、通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
前言:
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() 方法之间的区别在于方法参数不同,下面我们简要介绍一下。
- 提交 Runnable 任务
submit(Runnable task)
:这个方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以submit(Runnable task)
这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。- 提交 Callable 任务
submit(Callable<T> task)
:这个方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果。- 提交 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对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果