JUC工具包介绍
JUC是什么?
是并发变成工具包(java.util.concurrent)的英文缩写。
JUC的变迁历史:
- 从 JDK1.5 开始,Java 官⽅在 rt.jar 核⼼ jar 包⽂件中增加了 java.util.concurrent 并发包,由 Doug Lea ⼤⽜编写实现,并在后继的主要版本中不断对其增强、优化。
- 在 JDK1.6 中,主要对基础数据结构类进⾏了并发特性增强。
- 在 JDK1.7 中,主要对并发框架⼯具类进⾏了增强,新增了 ForkJoin 系列。
- 在 JDK1.8 中,主要对原⼦操作⼯具类进⾏了增强,增加了适⽤于更多场景的⼯具类。
JUC包目录结构
包路径 | 主要内容 | 典型类型 |
---|---|---|
java.util.concurrent | 提供很多种最基本的并发工具类,包含对各类数据结构的并发封装,并发框架主要接口 | CountDownLatch、CyclicBarrier、Semaphore、Exchanger、Phaser、BlockingQueue、ConcurrentHashMap、ThreadPoolExecutor、ForkJoinPool |
java.util.concurrent.atomic | 提供各类原子操作工具类 | AtomicInteger、DoubleAdder、LongAccumulator、AtomicReference |
java.util.concurrent.locks | 提供各类锁工具 | Lock、ReadWriteLock、ReentrantLock、ReentrantReadWriteLock、StampedLock |
JUC包内容分类
主要包含以下5个方面:
- 锁(locks): 提供适合各类场合的锁⼯具;
- 原⼦变量(atomic): 原⼦变量类相关,是构建⾮阻塞算法的基础;
- 并发框架(executor): 提供线程池相关类型;
- 并发容器(collections): 提供⼀系列并发容器相关类型;
- 同步⼯具(tools): 提供相对独⽴,且场景丰富的各类同步⼯具,如信号量(Semaphore)、闭锁(CountDownLatch)、栅栏(CyclicBarrier)等功能;
JUC解决的问题
用于解决多线程同步问题,给Java开发者提供便利的函数功能、数据结构。
Future介绍
Future设计模式从本质上是用来解决的根本的问题在于可以让一个任务在异步执行,而任务执行完之后我们可以在未来的某一个时间点上去获取到这个任务执行的结果,不管这个任务是执行失败或成功。可以理解为对线程Fork/Join的封装,使用变得更加简单便捷。
常用子类及核心方法
- 下图为本文主要介绍的2种Future子类
- Future接口的核心方法
- boolean cancel(boolean mayInterruptIfRunning)
尝试取消此任务 - boolean isCancelled()
如果此任务在正常完成之前被取消,则返回 true 。 - boolean isDone()
是否任务执行完了 - V get()
获取任务执行结果,如果任务没有完成,这个方法则会一直等待,没有超时机制 - V get(long timeout, TimeUnit unit)
获取任务执行结果,如果任务没有完成,这个方法则会一直等待,直到超过限定时间,超过限定时间则会抛出异常
应用场景
两个任务没有必然的前后关系,如果在一个线程中串行执行,就有些浪费时间,不如让两个线程去并行执行这两个任务,执行完了到主线程去汇报就可以了。
常用的组合方式
FutureTask
类继承体系
- 具备了Future接口对任务状态判断、取消及结果获取的能力
- 具备了Runnable接口处理业务的能力
构造方法public FutureTask(Callable callable)
- 结合Thread使用示例
public class FutureTaskTest {
public static void main(String[] args) {
FutureTask<String> ft1 = new FutureTask<>(new MyCallable("任务1", new Random().nextInt(10)));
FutureTask<String> ft2 = new FutureTask<>(new MyCallable("任务2", new Random().nextInt(10)));
FutureTask<String> ft3 = new FutureTask<>(new MyCallable("任务3", new Random().nextInt(10)));
// 启用子线程处理业务
new Thread(ft1).start();
new Thread(ft2).start();
new Thread(ft3).start();
try {
// 主线程处理其它事情
int sec = new Random().nextInt(10);
System.out.println("main, running ...\n will cost about " + sec + " s ");
TimeUnit.SECONDS.sleep(sec);
System.out.println("main, continue ...");
// 查看子线程运行状态
System.out.println("main, ft1, isDone=" + ft1.isDone() + ", isCancelled=" + ft1.isCancelled());
System.out.println("main, ft2, isDone=" + ft2.isDone() + ", isCancelled=" + ft2.isCancelled());
System.out.println("main, ft3, isDone=" + ft3.isDone() + ", isCancelled=" + ft3.isCancelled());
// 获取子线程返回结果
List<String> result = new LinkedList<>();
result.add(ft1.get());
result.add(ft2.get());
result.add(ft3.get());
System.out.println("main, finished !\n处理结果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
@RequiredArgsConstructor
static class MyCallable implements Callable<String> {
private final String name;
private final Integer sec;
@Override
public String call() throws Exception {
System.out.println("job:" + name + ", running ... will cost about " + sec + " s ");
TimeUnit.SECONDS.sleep(sec);
System.out.println("job:" + name + ", finished !");
return name + ":success";
}
}
}
- 结合JUC并发框架Executor使用示例
public class FutureTaskTest2 {
// 生产环境不建议使用Executors工具类产生的ExecutorService,存在一定的弊端(例如:无界队列会导致OOM问题)
final static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) {
FutureTask<String> ft0 = new FutureTask<>(new MyCallable("任务0", new Random().nextInt(10)));
FutureTask<String> ft1 = new FutureTask<>(new MyCallable("任务1", new Random().nextInt(10)));
FutureTask<String> ft2 = new FutureTask<>(new MyCallable("任务2", new Random().nextInt(10)));
FutureTask<String> ft3 = new FutureTask<>(new MyCallable("任务3", new Random().nextInt(10)));
// 启用子线程处理业务
executorService.submit(ft0);
executorService.submit(ft1);
executorService.submit(ft2);
executorService.submit(ft3);
try {
// 主线程处理其它事情
int sec = new Random().nextInt(10);
System.out.println("main, running ...\n will cost about " + sec + " s ");
TimeUnit.SECONDS.sleep(sec);
System.out.println("main, continue ...");
// 查看子线程运行状态
System.out.println("main, ft0, isDone=" + ft0.isDone() + ", isCancelled=" + ft0.isCancelled());
System.out.println("main, ft1, isDone=" + ft1.isDone() + ", isCancelled=" + ft1.isCancelled() + ", cancel=" + ft1.cancel(false));
System.out.println("main, ft2, isDone=" + ft2.isDone() + ", isCancelled=" + ft2.isCancelled() + ", cancel=" + ft2.cancel(true));
System.out.println("main, ft3, isDone=" + ft3.isDone() + ", isCancelled=" + ft3.isCancelled() + ", cancel=" + ft3.cancel(false));
// 获取子线程返回结果
List<String> result = new LinkedList<>();
try {
result.add(ft0.get());
} catch (CancellationException e) {
System.out.println("main, ft0, 被取消:" + e.getMessage());
} catch (ExecutionException e) {
System.out.println("main, ft0, 执行错误:" + e.getMessage());
} catch (InterruptedException e) {
System.out.println("main, ft0, 线程被中断:" + e.getMessage());
}
try {
result.add(ft1.get());
} catch (CancellationException e) {
System.out.println("main, ft1, 被取消:" + e.getMessage());
} catch (ExecutionException e) {
System.out.println("main, ft1, 执行错误:" + e.getMessage());
} catch (InterruptedException e) {
System.out.println("main, ft1, 线程被中断:" + e.getMessage());
}
try {
result.add(ft2.get());
} catch (CancellationException e) {
System.out.println("main, ft2, 被取消:" + e.getMessage());
} catch (ExecutionException e) {
System.out.println("main, ft2, 执行错误:" + e.getMessage());
} catch (InterruptedException e) {
System.out.println("main, ft2, 线程被中断:" + e.getMessage());
}
try {
result.add(ft3.get(7, TimeUnit.SECONDS));
} catch (CancellationException e) {
System.out.println("main, ft3, 被取消:" + e.getMessage());
} catch (ExecutionException e) {
System.out.println("main, ft3, 执行错误:" + e.getMessage());
} catch (InterruptedException e) {
System.out.println("main, ft3, 线程被中断:" + e.getMessage());
} catch (TimeoutException e) {
System.out.println("main, ft3, 超时:" + e.getMessage());
}
System.out.println("main, finished !\n处理结果:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
@RequiredArgsConstructor
static class MyCallable implements Callable<String> {
private final String name;
private final Integer sec;
@Override
public String call() throws Exception {
System.out.println("job:" + name + ", running ... will cost about " + sec + " s ");
TimeUnit.SECONDS.sleep(sec);
System.out.println("job:" + name + ", finished !");
return name + ":success";
}
}
}
另一个构造方法public FutureTask(Runnable runnable, V result)
- 与public FutureTask(Callable callable)类似,内部通过RunnableAdapter把Runnable接口适配为Callable并把result参数作为结果返回。
// 核心适配器类
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
// 普通方式调用run方法,并未开启线程
task.run();
// Runnable接口的run方法是没有返回值的】
return result;
}
}
CompletableFuture
- 上一小节介绍常用类FutureTask,然而实际的业务场景可能更为复杂,例如以下几种:
- 多个执行单元的串行请求
- 多个执行单元的并行请求
- 阻塞等待,串行的后面跟多个并行
- 阻塞等待,多个并行的执行完毕后才执行某个
- 串并行相互依赖
- 复杂场景
- 多个执行单元的串行请求
- 面对以上几种场景,我们需要一种能够简化并发变成的“任务编排”工具
- 这里推荐一个开源工具 京东零售 / asyncTool,有兴趣的可以看下。
- CompletableFuture通过提供一系列方法使“任务编排”变的更加简单
类继承体系
- CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。
- 同FutureTask相比,CompletableFuture最大的不同是支持流式(Stream)的计算处理,多个任务之间,可以前后相连,从而形成一个计算流。
常用的方法
- 创建任务
- .supplyAsync: 创建一个带返回值的任务
- .runAsync:创建一个不带返回值的任务
- 异步回调
- .thenApply、.thenApplyAsync:带返回值的异步调用函数, 有入参, 有出参
- .thenAccept、.thenAcceptAsync:不带返回值的异步回调函数, 有入参
- .thenRun、.thenRunAsync:不带返回值的异步回调函数,无入参
- .thenCombine、.thenCombineAsync:一般,在连接任务之间互相不依赖的情况下,可以使用thenCombine来连接任务,从而提升任务之间的并发度。
- .thenCompose、.thenComposeAsync:与 .thenApply 类似
- .whenComplete:当主任务出现异常时, 会终止任务,get的时候会抛出主任务的异常, 入参值为null, 否则正常运行
- .handle、.handleAsync:与whenComplete的作用有些类似,但是handle接收的处理函数有返回值,而且返回值会影响最终获取的计算结果。
- .exceptionally:异步任务出现异常调用的回调方法
- 其中,带Async后缀的函数表示需要连接的后置任务会被单独提交到线程池中,从而相对前置任务来说是异步运行的。除此之外,两者没有其他区别。
具体场景示例
多个执行单元的串行请求
- 当通过thenApply / thenAccept / thenRun连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。因此,这组函数主要用于连接前后有依赖的任务链。
public class CompletableFutureTest {
final static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 1");
return 1;
}, executorService);
CompletableFuture<Integer> future2 = future1.thenApply((p) -> {
System.out.println("compute 2");
return p + 10;
});
CompletableFuture<Integer> future3 = future2.thenApply((p) -> {
System.out.println("compute 3");
return p + 2;
});
System.out.println("result: " + future3.join());
}
}
多个执行单元的并行请求
- 在连接任务之间互相不依赖的情况下,可以使用thenCombine来连接任务,从而提升任务之间的并发度。注意:thenAcceptBoth、thenAcceptBothAsync、runAfterBoth、runAfterBothAsync的作用与thenConbime类似,唯一不同的地方是任务类型不同,分别是BiConumser、Runnable。
public class CompletableFutureTest2 {
final static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 1");
return 1;
}, executorService);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 2");
return 10;
}, executorService);
CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 3");
return 10;
}, executorService);
CompletableFuture<Integer> future = future1.thenCombine(future2.thenCombine(future3, (r2, r3) -> {
System.out.println("compute 2: r2=" + r2 + ", r3=" + r3);
return r2 + r3;
}), (r1, r23) -> {
System.out.println("compute 2: r1=" + r1 + ", r23=" + r23);
return r1 + r23;
});
System.out.println("result: " + future.join());
}
}
阻塞等待,串行的后面跟多个并行
public class CompletableFutureTest3 {
final static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 1");
return 1;
}, executorService);
CompletableFuture<Integer> future = future1.thenApply(r1 -> {
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 2");
return 10;
}, executorService);
CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 3");
return 10;
}, executorService);
CompletableFuture<Integer> future23 = future2.thenCombine(future3, (r2, r3) -> {
System.out.println("compute 2: r2=" + r2 + ", r3=" + r3);
return r2 + r3;
});
return r1 + future23.join();
});
System.out.println("result: " + future.join());
}
}
阻塞等待,多个并行的执行完毕后才执行
public class CompletableFutureTest4 {
final static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 1");
return 10;
}, executorService);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("compute 2");
return 10;
}, executorService);
CompletableFuture<Integer> future12 = future1.thenCombine(future2, (r1, r2) -> {
System.out.println("compute 12: r1=" + r1 + ", r2=" + r2);
return r1 + r2;
});
CompletableFuture<Integer> future = future12.thenCompose((r12) -> CompletableFuture.supplyAsync(() -> {
System.out.println("compute 3");
return r12 + 1;
}, executorService));
System.out.println("result: " + future.join());
}
}
串并行相互依赖
请自行尝试,略…
复杂场景
请自行尝试,略…
子线程异常处理
子线程中异常处理通常有以下3种处理方式:
子线程中使用try…catch…
class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
try {
// 业务操作
} catch (Exception e) {
System.out.println("错误:" + e.getMessage());
// 弥补逻辑
}
return null;
}
}
或
class MyRunnable implements Runnable {
@Override
public void run() {
try {
// 业务操作
} catch (Exception e) {
System.out.println("错误:" + e.getMessage());
// 弥补逻辑
}
}
}
为线程设置未捕获异常处理器UncaughtExceptionHandler
如果当前线程有异常处理器(默认没有),则优先使用该UncaughtExceptionHandler类;否则如果当前线程所属的线程组有异常处理器,则使用线程组的UncaughtExceptionHandler;否则,使用全局默认的DefaultUncaughtExceptionHandler;如果都没有的话,子线程就会退出。
为线程设置异常处理器,有以下2中方式:
- 通过Thread.setUncaughtExceptionHandler设置当前线程的异常处理器;
- 通过Thread.setDefaultUncaughtExceptionHandler设置整个程序默认的异常处理器
注意:子线程中发生了异常,如果没有任何类来接手处理的话,则直接退出,不会有任何的日志记录。所以,如果什么都不做的话,是会出现子线程任务没有执行陈宫,也没有任何日志提示的现象。
- 为单个线程设置异常处理器:
public class ThreadUncaughtExceptionTest {
public static void main(String[] args) {
// 设置全局默认异常处理器
Thread.setDefaultUncaughtExceptionHandler(new ThreadUncaughtExceptionTest2.ChildThreadExceptionHandler());
// 设置当前线程异常处理器
Thread thread = new Thread(new MyRunnable(0));
thread.setUncaughtExceptionHandler(new ChildThreadExceptionHandler());
thread.start();
}
static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("thread: " + t.getId() + ", error: " + e.getMessage());
}
}
@RequiredArgsConstructor
static class MyRunnable implements Runnable {
private final Integer a;
@Override
public void run() {
int b = 2;
System.out.println("b=" + b + ", b/a=" + b / a);
}
}
}
- 为线程组设置异常处理器:
public class ThreadGroupUncaughtExceptionTest {
public static void main(String[] args) {
ThreadGroup tg = new MyThreadGroup("TestThreadGroup");
Thread thread = new Thread(tg, new MyRunnable(0));
thread.start();
}
@RequiredArgsConstructor
static class MyRunnable implements Runnable {
private final Integer a;
@Override
public void run() {
int b = 2;
System.out.println("b=" + b + ", b/a=" + b / a);
}
}
static class MyThreadGroup extends ThreadGroup {
public MyThreadGroup(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("group: " + getName() + ", thread: " + t.getId() + ", error: " + e.getMessage());
}
}
}
- 为线程池设置异常处理器:
- 值得注意的是线程池的异常处理器,只有通过 execute 提交的任务,才能将它抛出的异常交给未捕获异常处理器,而通过 submit 提交的任务,无论是抛出的未检査异常还是已检査异常,都将被认为是任务返回状态的一部分。这也是为什么推荐通过捕获Future的get方法抛出异常类型的原因。
- 关于Executor并发框架的使用,不是本文主要内容,有机会可以另起一篇再作介绍。
public class ExecutorUncaughtExceptionTest {
final static ExecutorService executorService = Executors.newFixedThreadPool(10, r -> {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new ChildThreadExceptionHandler());
return thread;
});
public static void main(String[] args) {
// 启用子线程处理业务
try {
executorService.execute(new MyRunnable(0));
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
executorService.shutdown();
}
}
static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("thread: " + t.getId() + ", error: " + e.getMessage());
}
}
@RequiredArgsConstructor
static class MyRunnable implements Runnable {
private final Integer a;
@Override
public void run() {
int b = 2;
System.out.println("b=" + b + ", b/a=" + b / a);
}
}
}
通过捕获Future的get方法抛出异常类型(推荐)
- 参考FutureTask示例代码