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