一、简介
线程是什么,线程是一个任务的工作单元和执行单元。我们在用线程的时候知道,要创建线程,再执行线程。如果任务多的情况呢,会有大量的创建销毁线程的资源消耗,这时候就引入了线程池的概念。
JDK5开始,把工作单元和执行单元分开,工作单元包括Runnable和Callable,利用Executor来执行任务。
通过 Executor来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
二、Executor框架结构
1、任务: 包括被执行任务需要实现的接口,Runnable接口或者Callable接口。
2、任务的提交和执行: 包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduleThreadPoolExecutor)。ExecutorService接口中补充了任务的提交方法submit()。
3、任务的结果(异步计算): 包括接口Future和实现Future接口的FutureTask类。
Executor翻译过来就是执行器的意思,也就是说,Executor框架的目的就是调度任务进行执行,并得出异步计算的结果。
1、任务(Runnable和Callable接口)
主线程通过实现 Runnable 或者 Callable 接口,创建任务对象,用于后续执行。
Runnable或Callable接口的实现类都可以被 ThreadPoolExecutor 或ScheduledThreadPoolExecutor 执行。
区别:Runnable接口不会抛异常或者返回对象,但是 Callable 接口可以。
代码如下:
@FunctionalInterface
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;
}
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
2、任务的提交和执行
Executor常用的类图如下:
任务执行机制的核心接口 Executor源码如下:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
通过代码可以看出,其中定义了任务的执行。
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
ExecutorService接口中提供了submit()方法,用于提交需要返回值的任务。
代码如下:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
线程池会返回一个 Future 类型的对象。
3、任务的结果
Future存储的是未来产生的一个结果,其中的get()方法是阻塞的,用来获取返回值。
代码如下:
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
使用 get(long timeout,TimeUnit unit)方法的话,如果在 timeout 时间内任务还没有执行完,就会抛出 java.util.concurrent.TimeoutException。
FutureTask接口表示Future+Runnable,其类图如下:
FutureTask表示可以直接创建一个FutureTask对象,交给线程池去执行,并且有返回结果。
三、Executor框架使用
下面这个图出现的次数很多了:
1、主线程创建任务对象,这个任务对象实现了Runnable或 Callable接口。
2、把任务对象交给执行器ExecutorService去执行:
ExecutorService.execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
3、主线程可以直接创建一个FutureTask对象交给执行器去执行。
4、主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
四、 示例代码
1、Future
submit()和get()方法:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交并执行任务
Future<String> submit = executorService.submit(() -> {
try {
Thread.sleep(5000L);
System.out.println("5秒过去啦");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "我是返回值";
});
String s = submit.get();
System.out.println(s);
//关闭线程池
executorService.shutdown();
}
结果如下:
5秒过去啦
我是返回值
get(long timeout,TimeUnit unit)方法:
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交并执行任务
Future<String> submit = executorService.submit(() -> {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "我是返回值";
});
//3秒就不行了哦
String s = submit.get(3, TimeUnit.SECONDS);
System.out.println(s);
//关闭线程池
executorService.shutdown();
}
运行结果如下:
抛出异常,且线程阻塞未结束。
2、Callable
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//创建任务
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return "我是callable";
}
};
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交并执行任务
Future<String> submit = executorService.submit(callable);
//3秒就不行了哦
String s = submit.get(3, TimeUnit.SECONDS);
System.out.println(s);
//关闭线程池
executorService.shutdown();
}
执行结果如下:
我是callable
3、FutureTask
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建并执行任务
FutureTask<String> task = new FutureTask<>(()->{
return "aaaaaaa";
});
executorService.submit(task);
String s = task.get();
System.out.println(s);
//关闭线程池
executorService.shutdown();
}
执行结果如下:
aaaaaaa
五、总结&&常见对比
1、Runnable vs Callable
前者无返回值和抛异常,后者有。
2、execute vs submit
前者执行不需要返回值的任务,后者执行需要返回值的任务,返回值为Future。
3、shutdown vs shutdownNow
shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。(新任务停止,已存在的要执行完)
shutdownNow():关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。(停止所有任务并返回等待值)
4、isTerminated vs isShutdown
isTerminated () :调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true。
isShutdown():调用 shutdown() 方法后返回为 true。