多线程一锅乱炖(Runnable、Callable、Thread、Future、FutureTask、execute、submit、invokeAll、invokeAny)

多线程一锅乱炖(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 秒,超过后就会被回收,线程池执行流程如下:

  1. 向 SynchronousQueue 中提交任务
  2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
  3. 执行完任务的线程有 60 秒生存时间,空闲 60 秒的线程会被终止。

CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

FixedThreadPool
ExecutorService executorService = Executors.newFixedThreadPool(2);

核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中,线程池执行流程如下:

  1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
  2. 线程数等于核心线程数后,将任务加入阻塞队列
  3. 由于队列容量非常大,可以一直加
  4. 执行完任务的线程反复去队列中取任务执行

FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

SingleThreadExecutor
ExecutorService executorService = Executors.newSingleThreadExecutor();

相当于特殊的 FixedThreadPool,线程池执行流程如下:

  1. 线程池中没有线程时,新建一个线程执行任务
  2. 有一个线程以后,将任务加入阻塞队列,不停的加
  3. 唯一的这一个线程不停地去队列里取任务执行

SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行

ScheduledThreadPoolExecutor
ExecutorService executorService = Executors.newScheduledThreadPool(2);

执行流程如下:

  1. 添加一个任务
  2. 线程池中的线程从 DelayQueue 中取任务
  3. 线程从 DelayQueue 中获取 time 大于等于当前时间的 task
  4. 执行完后修改这个 task 的 time 为下次被执行的时间,然后再把这个 task 放回队列中

ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

ExecutorService提交任务的方法

execute、submit、invokeAll、invokeAll是ExecutorService中的四种执行并发任务的方法,其中execute继承父类Executor。他们的不同点在于:

  1. 方法签名不同:invokeAny方法接收一个Callable的集合作为参数,执行任务并返回其中任意一个任务的执行结果;invokeAll方法接收一个Callable的集合作为参数,执行所有任务,并返回一个Future对象的集合,每个Future对象对应一个任务的执行结果;submit方法接收一个Callable对象作为参数,执行任务并返回一个Future对象;execute方法接收一个Runnable对象作为参数,执行任务,但无返回值。
  2. invokeAny方法会阻塞当前线程,直到任一任务完成并返回该任务的执行结果;invokeAll方法会阻塞当前线程,直到所有任务完成并返回所有任务的执行结果;而submit方法和execute方法不会阻塞当前线程。
  3. invokeAny方法只返回其中一个任务的执行结果,无法获取所有任务的执行结果;invokeAll方法可以获取每个任务的执行结果;submit方法和execute方法可以通过Future对象获取任务的执行结果。
  4. invokeAny方法返回执行结果时,其他未完成的任务会被取消;invokeAll方法返回所有任务的执行结果,无论是否完成;submit方法和execute方法不会取消任务。
  5. 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));
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值