1、基础概念
并发:是在同一实体上的多个事件,是在一台处理器上“同时”处理多个任务,在同一时刻,实际上只有一个事件在发生,宏观并发,微观并行。
并行:在不同的实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家真的都在做事情,你做你的我做我的。
进程:简单的说,在系统中运行的一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源
线程:线程也被称为轻量级进程,在同一个进程内会有一个或者多个线程,是大多数操作系统进行时序调度的基本单元。
管程:也就是Monitor监视器,也就是我们平时说的锁
守护线程: 是一种特殊的线程为其他线程服务,在后台默默的完成一系列的系统性服务,比如GC垃圾回收线程,守护线程做为一个服务线程,没有服务对象就没有必要再继续运行下去,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了,所以假如系统只有守护线程的时候,java虚拟机会自动退出。
2、CompletableFuture
首先对于Future接口做一定的解释,Future接口可以为主线程开启一个分支任务,专门为主线程处理一些耗时和费力的任务。
Future是java5新加的一个接口,他提供了一中异步并行计算的功能,如果主线程需要执行一个很好事的任务,我们可以通过Futrue把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取处理的结果,
public interface Future<V> {
/**
* 1.尝试取消任务,如果任务已经完成或者被取消则会取消失败,也有可能因为其他原因取消失败,方法返回true表示取消成功,
* false表示取消失败,通常是因为任务已经开始了
* 2.如果任务还未开始并且被取消成功,那么这个任务再也不会被执行
* 3.如果任务已经开始,mayInterruptIfRunning参数决定执行该任务的线程是否需要中断任务,true表示正在执行任务的线程需要中断,false表示既然已经开始执行了,就执行完毕吧
* 4.如果方法返回true,那么isDone和isCancelled也会返回true
*/
boolean cancel(boolean mayInterruptIfRunning);
//如果任务在完成之前被取消,返回true
boolean isCancelled();
//如果任务已经完成,返回true,这里的完成不仅仅是代表正常完成,也有可能是异常终止,取消,这些都会返回true
boolean isDone();
//阻塞获取异步执行的结果
V get() throws InterruptedException, ExecutionException;
//指定超时时间的get超时
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
我们再之前的学习中可以了解到,使用实现Runnable接口可以开启多线程,使用Callable可以有返回值,同时要开启异步任务,啧啧啧该怎么办!
嘿嘿嘿FutureTask实现了所以我们想要的功能!来看看叭
/**
* @BelongsProject: juc_bilibili
* @BelongsPackage: com.bilibili.juc.xiaoli
* @Author: 斗痘侠
* @CreateTime: 2024-03-25 23:47
* @Description: TODO
* @Version: 1.0
*/
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> t = new FutureTask<>(new myThread());
Thread thread = new Thread(t);
thread.start();
String s = t.get();
System.out.println("得到的返回结果:"+s);
}
}
class myThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("执行业务");
return "我是返回结果!";
}
}
这里我们思考一个问题,事实上面的基本已经解决了我们的问题,但是现在想进一步的优化,如果我有多个异步的任务要执行呐,我是不是要每一次都要new一个新的线程来执行异步任务。这里我们可以采用池化技术,来对线程进行复用,防止频繁的创建关闭线程。
优化前:
public class FutureTaskPoolDemo {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
try {
// 任务一
TimeUnit.MICROSECONDS.sleep(500);
}catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
// 任务二
TimeUnit.MICROSECONDS.sleep(300);
}catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
// 任务三
TimeUnit.MICROSECONDS.sleep(300);
}catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-begin)+"ms");
System.out.println("main end");
}
}
优化后:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
long begin = System.currentTimeMillis();
FutureTask<String> futureTask1 = new FutureTask<String>(()->{
try {// 任务一
TimeUnit.MICROSECONDS.sleep(500);}catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "任务一完成";});
threadPool.submit(futureTask1);
System.out.println(futureTask1.get());
FutureTask<String> futureTask2 = new FutureTask<String>(()->{
try {// 任务二
TimeUnit.MICROSECONDS.sleep(300);}catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "任务二完成";});
threadPool.submit(futureTask2);
System.out.println(futureTask2.get());
FutureTask<String> futureTask3 = new FutureTask<String>(()->{
try {// 任务三
TimeUnit.MICROSECONDS.sleep(300);}catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "任务三完成";});
threadPool.submit(futureTask3);
System.out.println(futureTask3.get());
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-begin)+"ms");
System.out.println("main end");
threadPool.shutdown();
}
okok,上面的会造成什么事情呐,是这样的对于异步任务的结果使用get就可以得到,但是get方法会造成阻塞,get会一直等待异步任务完成, 使后面的异步任务之后的业务无法执行,你看看刚说了几下优点,现在一下子就来了一个致命的问题!
FutureTask中提供了一个IsDone方法,通过这个方法可以判断异步任务是否执行完毕,那么使用这个方法可以改善阻塞的现象。
public class FutureTaskPoolDemo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(()->{
System.out.println("开启任务");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务执行完毕!";
});
Thread thread = new Thread(futureTask, "t1");
thread.start();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"执行其他业务。。。");
while (true){
if (futureTask.isDone()){
//完成 获取异步任务的处理结果
System.out.println(futureTask.get());
break;
}else{
System.out.println("正在执行异步任务,还没完成");
//睡一会再问问
Thread.sleep(1000);
}
}
}
}
总结:Futrue对于结果的获取不是很友好,只能通过阻塞或者轮询的方式的到结果。当然对于简单的业务来说使用Future完全足够,但是对于我们想让他有一个回调机制,就是对于异步任务执行完毕之后,由异步任务通知我,而不是我频烦的询问异步任务好没好;创建异步任务的时候使用池化技术来优化线程的频烦创建和关闭;在进行多个异步任务的计算后,多个异步任务存在依赖关系,比如后一个任务计算结果需要前一个异步任务的值,那么这时候我们就想人为的干预异步任务的执行顺序。
好了,萝莉萝索了那么久针对上面的问题总要有一个东西要实现这个功能叭!谁--CompleableFuture
这里Future我们知道,那么这也个CompletionStage是什么东西,CompleionStage代表了异步计算过程中的某个阶段,一个阶段完成之后可能会触发另外一个阶段。
runAsync没有返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public class CompletableFutureDemo1 {
public static void main(String[] args) {
//没有线程池
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("开启没有线程池异步任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("开启有线程池异步任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, threadPool);
threadPool.shutdown();
}
}
supplyAsync有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
public class CompletableFutureDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("开启没有线程池异步任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "没有线程池异步任务-执行完毕";
});
System.out.println(future1.get());
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("开启有线程池异步任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "执行完毕";
}, threadPool);
System.out.println(future2.get());
threadPool.shutdown();
}
}
没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。如果指定线程池,则使用的是我们自定义的或者是特别指定的线程池执行异步代码。
空口无凭,他是如何解决获取异步任务阻塞的问题,以及轮询的方式查询是否执行完毕!
public class CompletableFutureDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"执行业务");
int result = ThreadLocalRandom.current().nextInt(10);
try {
System.out.println("业务正在执行。。。");
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return result;
}).whenComplete((v,e)->{
if (e == null){
System.out.println("异步任务执行成功,结果是:"+v);
}
}).exceptionally(e->{
System.out.println("异步任务执行失败,异常是:"+e);
return null;
});
System.out.println("main线程执行其他业务。。。");
//防止主线程提前结束,导致CompletableFuture没有机会执行
Thread.sleep(6000);
System.out.println("异步任务结果:"+future.get() );
}
}
你看是不是解决了阻塞以及询问的方式,但是这里compleableFutrue像是一个守护线程,要是主线程执行完毕之后,如果CompletableFuture中的异步任务还没有执行完毕,那么异步任务会关闭,这可不行,所以上面我们选择让主线程睡眠一会,但是这样做也不太好吧,明明主线程没事干了,还要让他睡一会!
public class CompletableFutureDemo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> future = null;
try {
future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"执行业务");
int result = ThreadLocalRandom.current().nextInt(10);
try {
System.out.println("业务正在执行。。。");
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return result;
},threadPool).whenComplete((v,e)->{
if (e == null){
System.out.println("异步任务执行成功,结果是:"+v);
}
}).exceptionally(e->{
System.out.println("异步任务执行失败,异常是:"+e);
return null;
});
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
System.out.println("main线程执行其他业务。。。");
System.out.println("异步任务结果:"+future.get() );
}
}
使用默认的线程池的话会出现,主线程执行完毕之后,导致CompelableFutrue的异步任务也会随之关闭,当我们使用自定义的线程就不用考虑这个问题。
获取值的方式
可以使用get,当然也可以使用join,但是对于get的方式的话,需要我们抛出异常,而join则不需要!
try {
System.out.println("异步任务结果:"+future.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务结果:"+future.join());
public class CompletableFutureMallDemo1 {
static List<NetMall1> list = Arrays.asList(
new NetMall1("jd"),
new NetMall1("dd"),
new NetMall1("tb")
);
//一步一步
public static List<String> getPrice(List<NetMall1> list, String p){
List<String> collect = list.stream().map(netMall -> String.format(p + " in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(p))).collect(Collectors.toList());
return collect;
}
public static List<String> getPriceByCompletableFuture(List<NetMall1> list, String p){
List<String> stringList = list.stream()
.map(netMall
-> CompletableFuture.supplyAsync(()
-> String.format(p + " in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(p))))
.collect(Collectors.toList())
.stream()
.map(s
-> s.join())
.collect(Collectors.toList());
return stringList;
}
public static void main(String[] args) {
/* //计算耗时
long s = System.currentTimeMillis();
List<String> stringList = getPrice(list, "mysql");
for (String s1 : stringList) {
System.out.println(s1);
}
long b = System.currentTimeMillis();
System.out.println("耗时:"+(b-s)+"毫秒");*/
//计算耗时
long s = System.currentTimeMillis();
List<String> stringList = getPriceByCompletableFuture(list, "mysql");
for (String s1 : stringList) {
System.out.println(s1);
}
long b = System.currentTimeMillis();
System.out.println("耗时:"+(b-s)+"毫秒");
}
}
class NetMall1 {
@Getter
private String netMallName;
public NetMall1(String netMallName) {
this.netMallName = netMallName;
}
public double calcPrice(String productName){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
}
}
2.1、方法
2.1.1、获取结果和触发计算
获取结果:
-
public T get() 不见不散
-
public T get(long timeout, TimeUnit unit) 过期不候
-
public T join()
-
public T getNow(T valueIfAbsent)
前面的三个我们都说过,那么这个getNow该如何使用呐,对于异步任务执行,如果没有执行完毕,那么给一个备胎值,如果执行完毕将处理结果给我。valueIfAbsent就是备胎
主动触发计算:
-
public boolean complete(T value)
这个方法其实和getNew很类似,如果在调用方法的时候异步任务没有执行完毕,那么就直接返回value作为备胎数据,当然如果执行完毕,可以正常的获取结果。
2.1.2、对计算结果进行处理
-
public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn)
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("111");
return 1;
}, threadPool).thenApply(f -> {
System.out.println("222");
return f + 2;
}).thenApply(f -> {
System.out.println("333");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("----计算结果: " + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println(e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "----主线程先去忙其它任务");
threadPool.shutdown();
}
但是这种方式,由于存在依赖关系,当前步错,不走下一步,当前步骤有异响的话就叫停,哈哈哈那么接下来就要处理出现这种问题,该用什么方法!
public <U> CompletableFuture<U> handle( BiFunction<? super T, Throwable, ? extends U> fn)
public class API2 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("111");
return 1;
}, threadPool).handle((f,e) -> {
int i=10/0;
System.out.println("222");
return f + 2;
}).handle((f,e) -> {
System.out.println("333");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("----计算结果: " + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println(e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "----主线程先去忙其它任务");
threadPool.shutdown();
}
}
虽然有依赖关系,但是如果存在异常的话还是可以往下一步走,根据带的异常信息来进行处理
2.1.3、对计算结果进行消费
-
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public class API3 {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println("111");
return 1;
}).thenApply(f -> {
System.out.println("222");
return f + 2;
}).thenApply(f -> {
System.out.println("333");
return f + 3;
}).thenAccept(r->{
System.out.println("----计算结果: " + r);
});
}
}
接收任务处理结果,直接处理不做返回。
补充:
-
thenRun(Runable runable)任务A执行完执行B,并且B不需要A结果
-
thenAccept(Consumer action) 任务A执行完执行B,B需要A的结果,但是任务B不需要返回值
-
thenApply(Function fn)任务A执行完执行B,B需要A的结果,同时任务B返回值
2.1.4、对计算速度进行选用
public class API4 {
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("A:执行业务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "计划A";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("B:执行业务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "计划B";
});
CompletableFuture<String> future = future1.applyToEither(future2, result -> {
return result + "最终计划";
});
System.out.println(future.join());
}
}
2.1.5、对计算结果进行合并
两个CompletionStage任务都完成之后,最终能把两个任务的结果一起交给thenCommbine来处理,先完成的等待其他的分支任务。
public class API5 {
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("A:执行业务");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A:执行业务完毕");
return 2;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("B:执行业务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B:执行业务完毕");
return 3;
});
CompletableFuture<Integer> future = future1.thenCombine(future2, (f1, f2) -> {
return f1 + f2;
});
System.out.println(future.join());
}
}
2.2、线程池和CompleableFuture
如果没有指定线程池的话,默认使用的是ForkJoinPool,如果传入了一个自定义线程池,当你第一个任务在执行的时候传入了一个自定的线程池,使用thenRun方法执行的第二个任务时,默认使用的线程池时和第一个任务的一致,但是如果调用的方法时thenRunAsync时,即使第一个任务时你自己定义的线程池但是第二个任务使用的是默认的ForkJoin线程池。