JUC基础知识
多线程
管程
Monitor,也就是平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码块,JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现,每个对象实例都会有一个Monitor对象,底层是由C++语言来实现。
-
补:Monitor对象并不会随着Java对象的销毁而销毁。
Java对象是在Java堆中创建的,而Monitor是与Java对象关联的、由JVM内部管理的机制。Monitor的创建和销毁并不直接对应于Java对象的创建和销毁。更确切地说,当Java对象被用作同步锁时,JVM会为该对象关联一个Monitor;而当该对象不再被用作同步锁,或者对象被垃圾回收时,与其关联的Monitor可能会被JVM内部释放或重新利用。
?:为什么是同步机制,不是互斥机制?
- 同步机制与互斥机制并不矛盾,同步机制包含了互斥以及协作
线程分类
用户线程
工作线程,完成业务操作
守护线程
一种特殊的为其它线程服务的线程,在后台默默完成一些系统性的任务(典型:垃圾回收线程)。做为一个服务线程,当服务对象没有了,就会伴随JVM一同结束工作。
?:怎么设置成守护线程?
Thread t = new Thread(() -> {});
t.setDaemon();
t.start();
/**
必须在start()开始前设置,不然会抛异常
*/
CompletableFuture
Future接口
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
- 功能
定义操作异步任务执行的一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
- 实现类 :FutureTask
FutureTask<String> futureTask = new FutureTask<>(() -> "Hello");
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
- 优点
Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
- 缺点
get()
阻塞一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
isDone()
轮询轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
FutureTask<String> futureTask = new FutureTask<>(() -> { TimeUnit.SECONDS.sleep(5); return "hello world"; }); new Thread(futureTask).start(); while (true) { if (futureTask.isDone()) { try { System.out.println(futureTask.get()); return; } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } else { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("程序正在运行"); } }
CompletableFuture 对 Future的改进
Future 在实际杂的任务中遇到了众多问题:
- 轮询耗时,不能回调
- 怎么更好与线程池组合使用
- 多个任务同步问题
在jdk8中出现了CompletableFuture
- 类架构
- 创建
- 回调
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (result > 5) { //模拟产生异常情况
int i = 10 / 0;
}
System.out.println("----------1秒钟后出结果" + result);
return result;
}, executorService).whenComplete((v, e) -> {
if (e == null) {
System.out.println("计算完成 更新系统" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "先去完成其他任务");
executorService.shutdown();
chain链式调用
@Accessors(chain = true)//开启链式调用
常用函数
-
get()
-
get(long timeout,TimeUnit unit)
-
join – 不抛异常
-
getNow() 完成返回正常值,否则备胎值
-
complate(T val) 是否打断get()直接返回val
-
thenApply() 计算存在依赖关系,串行化,且当前异常不进行下一步
-
handle() 存在依赖,串行化,异常也进行下一步
-
thenAccpt() 接受任务结果,不返回
-
thenRun() A执行完执行B,不需要A的结果
-
applyToEither() 谁快用谁
-
thenCombine 合并计算结果
线程池
如果没有传入自定义线程池,都用默认线程池ForkJoinPool
传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池
调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
备注:可能是线程处理太快,系统优化切换原则, 直接使用main线程处理,thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。
如何在Springboot如何在注册线程池呢?点击查看