多线程一锅乱炖(Runnable、Callable、Thread、Future、FutureTask、execute、submit、invokeAll、invokeAny)
在多线程执行周期中,基础的流程可以简略分为(新建执行任务 -> 创建任务进程 -> 任务装配进程 -> 启动/执行线程 -> 获取结果/状态),各类元素负责的流程如下所示:
元素名称/流程 | 新建执行任务 | 创建任务进程 | 任务装配进程 | 启动/执行线程 | 获取结果/状态 |
Runnable接口 | ✔ | ||||
Callable接口 | ✔ | ||||
Thread类 | ✔ | ✔ | ✔ | ✔ | ✔ |
Future接口 | ✔ | ||||
FutureTask类 | ✔ | ✔ | |||
ExecutorService接口 | ✔ | ✔ | ✔ |
准备阶段
Runnable接口和Callable接口
- Runnable接口只声明了一个run()方法:
public interface Runnable {
public abstract void run();
}
// 新建Runnable和编辑任务
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("任务编辑");
}
};
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
- Callable接口只声明了一个call()方法:
public interface Callable<V> {
V call() throws Exception;
}
// 新建Callable和编辑任务(返回String)
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("任务编辑");
return "返回结果";
}
};
这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Thread类(Runnable实现类)
Thread类实现了Runnable接口,做到了**(新建任务 -> 新建线程 -> 启动线程 -> 控制线程)**全过程。
- 属性:
public
class Thread implements Runnable {
/* 表示Thread的名字,可通过Thread类的构造器中的参数来指定线程名字 */
private volatile String name;
/* 表示Thread的优先级(最大值为10,最小值为1,默认值为5 */
private int priority;
/* daemon表示Thread是否为守护线程 */
private boolean daemon = false;
/* 表示Thread要执行的任务 */
private Runnable target;
}
- 方法:
Thread thread = new MyThread(); // 新建线程类,重写run或者装配Runnable/Callable
thread.start();/* start()用来启动一个线程 */
thread.run();/* 继承Thread类必须重写run方法定义要执行的任务,不需要用户来调用,当start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run()方法体去执行具体的任务。 */
thread.sleep(long millis);/* 让CPU去执行其他线程,但不会释放锁 */
thread.yield();/* 让CPU去执行其他的线程,不会释放锁。但不控制具体的交出CPU的时间 */
thread.join();/* 在main线程中调用join()方法,main线程会等待线程执行完毕或者等待一定的时间。 */
thread.interrupt();/* interrupt方法使线程抛出一个异常,可以用来中断一个线程 */
Future(获取进程状态/结果的接口)
Future接口是对Runnable/Callable的更进一步的封装可以对于Runnable/Callable任务的执行结果进行取消、查询是否完成、获取结果。可以通过get方法获取执行结果,但该方法会阻塞直到任务返回结果。
public interface Future<V> {
/* 取消任务,取消成功返回true,失败返回false,mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务 */
boolean cancel(boolean mayInterruptIfRunning);
/* 表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true */
boolean isCancelled();
/* 表示任务是否已经完成,若任务完成,则返回true */
boolean isDone();
/* 用来获取执行结果,这个方法会产生阻塞!!!会一直等到任务执行完毕才返回 */
V get() throws InterruptedException, ExecutionException;
/* 用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null */
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask类(Future实现类)
FutureTask实现了RunnableFuture接口(Runnable + Future),相比于同为实现类的Thread,仅仅做到了**(控制进程状态 + 获取结果/状态)**。
// FutureTask类(Future实现类)
public class FutureTask<V> implements RunnableFuture<V>{
// code
}
// RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
执行阶段
ExecutorService(线程池接口)
ThreadPoolExecutor是线程池的底层构造方法,其中参数最多的构造方法如下所示:
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大线程池大小
long keepAliveTime, // 线程最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 线程等待队列
ThreadFactory threadFactory, // 线程创建工厂
RejectedExecutionHandler handler) // 拒绝策略
ExecutorService通过Executors工厂类提供四种**预设特化(固定某些参数的ThreadPoolExecutor)**线程池:
Executors工厂类 | 功能描述 |
Executors.newCachedThreadPool() | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,否则新建线程。(线程最大并发数不可控制) |
Executors.newFixedThreadPool(int nThreads) | 创建一个固定大小(nThreads)的线程池,可控制线程最大并发数,超出的线程会在队列中等待 |
Executors.newScheduledThreadPool() | 创建一个定时线程池,支持定时及周期性任务执行 |
Executors.newSingleThreadExecutor() | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行 |
CachedThreadPool
ExecutorService executorService = Executors.newCachedThreadPool();
CachedThreadPool 没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收,线程池执行流程如下:
- 向 SynchronousQueue 中提交任务
- 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
- 执行完任务的线程有 60 秒生存时间,空闲 60 秒的线程会被终止。
CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。
FixedThreadPool
ExecutorService executorService = Executors.newFixedThreadPool(2);
核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中,线程池执行流程如下:
- 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
- 线程数等于核心线程数后,将任务加入阻塞队列
- 由于队列容量非常大,可以一直加
- 执行完任务的线程反复去队列中取任务执行
FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
SingleThreadExecutor
ExecutorService executorService = Executors.newSingleThreadExecutor();
相当于特殊的 FixedThreadPool,线程池执行流程如下:
- 线程池中没有线程时,新建一个线程执行任务
- 有一个线程以后,将任务加入阻塞队列,不停的加
- 唯一的这一个线程不停地去队列里取任务执行
SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
ScheduledThreadPoolExecutor
ExecutorService executorService = Executors.newScheduledThreadPool(2);
执行流程如下:
- 添加一个任务
- 线程池中的线程从 DelayQueue 中取任务
- 线程从 DelayQueue 中获取 time 大于等于当前时间的 task
- 执行完后修改这个 task 的 time 为下次被执行的时间,然后再把这个 task 放回队列中
ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
ExecutorService提交任务的方法
execute、submit、invokeAll、invokeAll是ExecutorService中的四种执行并发任务的方法,其中execute继承父类Executor。他们的不同点在于:
- 方法签名不同:invokeAny方法接收一个Callable的集合作为参数,执行任务并返回其中任意一个任务的执行结果;invokeAll方法接收一个Callable的集合作为参数,执行所有任务,并返回一个Future对象的集合,每个Future对象对应一个任务的执行结果;submit方法接收一个Callable对象作为参数,执行任务并返回一个Future对象;execute方法接收一个Runnable对象作为参数,执行任务,但无返回值。
- invokeAny方法会阻塞当前线程,直到任一任务完成并返回该任务的执行结果;invokeAll方法会阻塞当前线程,直到所有任务完成并返回所有任务的执行结果;而submit方法和execute方法不会阻塞当前线程。
- invokeAny方法只返回其中一个任务的执行结果,无法获取所有任务的执行结果;invokeAll方法可以获取每个任务的执行结果;submit方法和execute方法可以通过Future对象获取任务的执行结果。
- invokeAny方法返回执行结果时,其他未完成的任务会被取消;invokeAll方法返回所有任务的执行结果,无论是否完成;submit方法和execute方法不会取消任务。
- invokeAny方法只能返回任务的执行结果,无法获取任务的执行状态;invokeAll方法可以获取每个任务的执行状态和执行结果;submit方法和execute方法无法直接获取执行状态和执行结果,需要通过Future对象的方法来获取。
总而言之:
- execute方法适用于不关心任务结果的情况;
- submit方法适用于需要获取任务结果的情况;
- invokeAll方法适用于提交并等待所有任务完成的情况;
- invokeAny方法适用于只关心最先完成的任务结果的情况。
execute
用于提交一个Runnable任务执行,并不返回任何结果。
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(() -> {
// 执行任务的逻辑
System.out.println("Task is executing");
});
executor.shutdown();
submit
用于提交一个Callable或Runnable任务执行,并返回一个表示任务结果的Future对象。
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<integer> future = executor.submit(() -> {
// 执行任务的逻辑, 返回计算结果
return 42;
});
executor.shutdown();
int result = future.get(); // 获取任务结果</integer>
invokeAll
用于提交多个Callable任务执行,并返回一个包含所有任务结果的列表(List)。
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(() -> 1);
tasks.add(() -> 2);
tasks.add(() -> 3);
List<Future<Integer>> futures = executor.invokeAll(tasks);
executor.shutdown();
for (Future<Integer> future : futures) {
int result = future.get();
System.out.println("Task result: " + result);
}
invokeAny
用于提交多个Callable任务执行,并返回其中一个任务的结果。只要有一个任务完成或抛出异常,该方法就会返回。
ExecutorService executor = Executors.newCachedThreadPool();
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(() -> 1);
tasks.add(() -> 2);
tasks.add(() -> 3);
int result = executor.invokeAny(tasks);
executor.shutdown();
System.out.println("Task result: " + result);
流程与方法
方法一
首先提供一个待使用的任务:
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
return "test";
}
};
对于常见多线程流程,提供以下三种方法:
// 第一种方法
FutureTask<String> ft = new FutureTask<>(c);
new Thread(ft).start();
System.out.println("ft = " + ft.get());
// 第二种方法
FutureTask<String> ft = new FutureTask<>(c);
ExecutorService es = Executors.newCachedThreadPool();
es.submit(ft);
System.out.println("ft = " + ft.get());
// 第三种方法
ExecutorService es = Executors.newCachedThreadPool();
Future<String> ft = es.submit(c);
System.out.println("ft = " + ft.get());
方法二
提供一个待使用的方法:
public <T> T function(T input) {
return input;
}
提供一个线程工厂
public class ThreadPoolFactory {
// 通用线程池,执行耗时少的任务
public static final ThreadPoolExecutor THREAD_POOL = new ThreadPoolExecutor(8, 20, 3,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("MyThreadPoolFactory-executor-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());
// 专用线程池,无等待队列
public static final ThreadPoolExecutor SP_THREAD_POOL = new ThreadPoolExecutor(1, 200, 30,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder().setNameFormat("SP-ThreadPoolFactory-executor-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());
}
在需要异步时使用匿名函数方法调用普通函数即可
int a = 1;
Future<Integer> b = ThreadPoolFactory.THREAD_POOL.submit(() -> function(a));
Integer c = b.get();
System.out.println("c = " + function(c));