10. 线程池-Executors 常见用法示例

java 多线程系列文章列表, 请查看目录: 《java 多线程学习笔记》

1. 创建异步任务

为了测试方法, 笔者创建一个有返回值的Callable类, 创建一个无返回值的Runnable 类.

1.1 自定义Callable

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        int costTime = new Random().nextInt(10);
        String name = Thread.currentThread().getName();

        System.out.println(name + "-任务执行开始, 耗时:" + costTime + " 秒");
        Thread.sleep(costTime * 1000);
        System.out.println(name + "-任务执行结束, 耗时:" + costTime + " 秒");
        return name;
    }
}

1.2 自定义Runnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        try {
            String start = LocalDateTime.now().toString();
            Thread.sleep(3000l);
            String end = LocalDateTime.now().toString();
            System.out.printf("开始:%s, 结束:%s, 耗时: 3秒\n", start, end);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. submit用法

  • submit 提交单个异步任务, 不会阻塞主线程
  • submit 返回Furture, 可以监测异步任务执行是否完成, 或直接取消异步任务.

2.1 测试用例

public static void main(String[] args) throws ExecutionException, InterruptedException {

    // 1. 创建单线程线程池
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    // 2. 提交2个异步任务,不阻塞主线程
    executorService.submit(new MyRunnable());
    Future<String> future = executorService.submit(new MyCallable());

    // 3. 主线程执行其它业务逻辑
    System.out.println(" 主线程执行其它业务逻辑....");

    // 4. 阻塞式获取异步线程结果
    System.out.println("阻塞获取线程执行结果:" + future.get());

    System.out.println(" 主线程继续执行其它业务逻辑...");

    // 5.关闭线程池
    executorService.shutdown();
}

2.2 控制台输出

  • 异步线程提交之后, 并没有影响主线程的执行
 主线程执行其它业务逻辑....
开始:2019-08-31T13:25:14.427, 结束:2019-08-31T13:25:17.427, 耗时: 3秒
pool-1-thread-1-任务执行开始, 耗时:4 秒
pool-1-thread-1-任务执行结束, 耗时:4 秒
阻塞获取线程执行结果:pool-1-thread-1
 主线程继续执行其它业务逻辑...

3. invokeAll 用法

  • invokeAll 用于提交一组异步事务, 会阻塞主线程, 等这一组事务全部完成之后, 才会继续主线程
  • invokeAll 返回List<Future>类型, 返回每个异步任务的执行结果

3.1 测试用例

public static void main(String[] args) throws ExecutionException, InterruptedException {

    // 1. 创建固定线程池
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 2. 创建一组异步任务
    List<Callable<String>> list = new ArrayList<>();
    list.add(new MyCallable());
    list.add(new MyCallable());
    list.add(new MyCallable());

    // 3. 提交一组异步任务, 会阻塞主线程
    List<Future<String>> futures = executorService.invokeAll(list);

    // 4. 主线程被阻塞, 不能执行其它任务
    System.out.println("主线程被阻塞, 不能执行其它任务");

    // 5. 获取返回结果, 此时所有异步任务已执行完毕, 所以不再有阻塞
    System.out.println("主线程获取异步任务返回结果, 无阻塞:");
    for (Future<String> future : futures) {
        System.out.println(future.get());
    }

    // 6.关闭线程池
    executorService.shutdown();
}

3.2 控制台输出

从控制台输出可以发现, invokeAll 方法会阻塞主线程的执行, 必须等本次提交的所有异步任务执行完之后, 才会继续执行.

pool-1-thread-2-任务执行开始, 耗时:2 秒
pool-1-thread-1-任务执行开始, 耗时:3 秒
pool-1-thread-3-任务执行开始, 耗时:1 秒
pool-1-thread-3-任务执行结束, 耗时:1 秒
pool-1-thread-2-任务执行结束, 耗时:2 秒
pool-1-thread-1-任务执行结束, 耗时:3 秒
主线程被阻塞, 不能执行其它任务
主线程获取异步任务返回结果, 无阻塞:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3

4. invokeAny 用法

  • invokeAny 提交的一组异步任务, 只有一个返回结果, 当任一异步任务执行结束后,会强制取消其它异步任务
  • invokeAny 同样会阻塞主线程

4.1 测试用例

public static void main(String[] args) throws ExecutionException, InterruptedException {

    // 1. 创建固定线程池
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 2. 创建一组异步任务
    List<Callable<String>> list = new ArrayList<>();
    list.add(new MyCallable());
    list.add(new MyCallable());
    list.add(new MyCallable());

    // 3. 提交一组异步任务, 会阻塞主线程
    String result = executorService.invokeAny(list);

    // 4. 主线程被阻塞, 不能执行其它任务
    System.out.println("主线程被阻塞, 不能执行其它任务");

    // 5. 获取返回结果, 此时所有异步任务已执行完毕, 所以不再有阻塞
    System.out.println("获取异步线程组返回的唯一一个结果, 无阻塞:" + result);

    // 6.关闭线程池
    executorService.shutdown();
}

4.2 控制台输出

  • 从控制台输出可以看出, 只有一个异步任务输出了结束日志, 证明只有一个线程结束.
  • 当有任意一个异步任务结束时, 会取消其它所有异步任务
pool-1-thread-1-任务执行开始, 耗时:2 秒
pool-1-thread-2-任务执行开始, 耗时:4 秒
pool-1-thread-3-任务执行开始, 耗时:4 秒
pool-1-thread-1-任务执行结束, 耗时:2 秒
主线程被阻塞, 不能执行其它任务
获取异步线程组返回的唯一一个结果, 无阻塞:pool-1-thread-1

5. 定时调度线程池

Executors 线程池创建的定时调度线程池, 返回对象为ScheduledExecutorService. 其中scheduleAtFixedRate 和 scheduleWithFixedDelay 用于循环调度, 使用时有些许区别:

  • 相同点: 任意时刻, 同一类型异步任务只能运行一个.
  • 不同点:
    • scheduleAtFixedRate: 如果任务未执行完后又到了触发时间, 那么会等待当前任务执行结束后, 立刻触发下次任务
    • scheduleWithFixedDelay: 如果任务未执行完后又到了触发时间, 那么会等待当前任务执行结束, 当前任务结束后, 依然会延迟固定时间间隔后, 再次触发下次任务
  • 需要注意的是: 当异步任务抛出未捕获异常时, 终止重复调度

5.1 测试用例

笔者测试用例为, 延迟1秒后开始以2秒时间间隔重复调度.

public static void main(String[] args) throws Exception{

    // 1. 创建调度线程池
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

    // 2. 创建异步任务, 无返回值
    MyRunnable runnable = new MyRunnable();

    // 3. 以固定频率运行
//        scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 2, TimeUnit.SECONDS);
    scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 2, TimeUnit.SECONDS);
}

5.2 scheduleAtFixedRate 输出

从测试结果发现, 当上次任务结束之后, 立刻会触发下次调度. 因为任务执行时长3秒已经超过了设置的时间间隔2秒

开始:2019-08-31T13:25:14.427, 结束:2019-08-31T13:25:17.427, 耗时: 3秒
开始:2019-08-31T13:25:17.458, 结束:2019-08-31T13:25:20.459, 耗时: 3秒
开始:2019-08-31T13:25:20.459, 结束:2019-08-31T13:25:23.459, 耗时: 3秒
开始:2019-08-31T13:25:23.459, 结束:2019-08-31T13:25:26.460, 耗时: 3秒

5.3 scheduleWithFixedDelay 输出

从测试结果发现, 无论单次任务执行耗时多久, 当任务执行结束之后, 都会延时固定时间间隔后, 再次触发下次任务.

开始:2019-08-31T13:26:34.576, 结束:2019-08-31T13:26:37.577, 耗时: 3秒
开始:2019-08-31T13:26:39.602, 结束:2019-08-31T13:26:42.602, 耗时: 3秒
开始:2019-08-31T13:26:44.603, 结束:2019-08-31T13:26:47.603, 耗时: 3秒
开始:2019-08-31T13:26:49.608, 结束:2019-08-31T13:26:52.608, 耗时: 3秒
开始:2019-08-31T13:26:54.608, 结束:2019-08-31T13:26:57.608, 耗时: 3秒

6. schedule-延迟执行

schedule 延迟执行并不会阻塞主线程, 只会执行一次.

6.1 测试用例

public static void main(String[] args) throws Exception{

    // 1. 创建调度线程池
    ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    // 2. 创建异步任务, 无返回值
    MyCallable runnable = new MyCallable();

    // 3. 延时3秒后执行
    ScheduledFuture<String> future = scheduledExecutorService.schedule(runnable, 3, TimeUnit.SECONDS);

    // 4. 不阻塞主线程执行其它业务逻辑
    System.out.println("执行其它业务逻辑...");

    // 5. 阻塞获取异步任务执行结果
    System.out.println("阻塞获取异步任务结果:" + future.get());

    // 6. 关闭线程池
    scheduledExecutorService.shutdown();

}

6.2 测试输出

执行其它业务逻辑...
pool-1-thread-1-任务执行开始, 耗时:4 秒
pool-1-thread-1-任务执行结束, 耗时:4 秒
阻塞获取异步任务结果:pool-1-thread-1
发布了321 篇原创文章 · 获赞 676 · 访问量 147万+

猜你喜欢

转载自blog.csdn.net/zongf0504/article/details/100186150