JUC高级编程

CompletableFuture

1. Future接口理论知识

  1. Future接口定义了异步任务执行的一些方法,如获取异步任务的结果、取消任务的执行、判断任务是否取消、判断任务是否完毕
  2. Future是异步任务的执行方法,让子线程去执行某些任务,然后主线程继续执行,过段时间后获取主线程的执行结果
  3. 通过在主线程中创建异步线程,让异步线程去执行操作,然后再调用方法获得结果
  4. FutureTask就是Future接口的实现类
  5. 异步任务可以获得执行结果,普通线程无法获得执行结果;可以在创建FutureTask时设置返回值类型,然后传入一个Callable接口实现类,重写call()时可以返回结果

2. FutureTask实现类异步任务

  1. 并发编程要满足 多线程、有返回、异步任务,故要同时满足Thread\Callable\Future,而创建Thread必须传入Runnable接口实现类(多线程),且使用返回值必须使用Callable接口实现类(有返回),实现异步任务必须是Future接口实现类(异步任务);故使用FutureTask,其是RunnableFuture接口的实现了,而且可以传入Callable实现类来创建对象
  2. FutureTask是RunnableFuture接口的实现类,其可以通过Callable接口实现类来创建对象,然后根据该对象创建的Thread线程就可以实现异步任务![[attachments/Pasted image 20241201224826.png]]
  3. FutureTask是Future接口和Runnable接口共同的实现类,用来实现异步任务并返回结果,且FutureTask实现类没有空参构造,只支持使用Callable实现类或者Runnable进行构造(因为实现了Runnable接口,必须重写run()),当创建好FutureTask对象后,此时就可以传入Thread中来创建线程,因为实现了Runnable接口,故可以直接传入来构建线程
  4. 创建线程Thread时可以通过Runnable接口实现类来创建同步线程,此时不可以返回结果;也可以通过Callable接口实现类先创建一个FutureTask对象,然后再创建线程,此时创建的是异步任务线程,可以获得返回值
  5. 创建线程Thread只可以传入Runnable实现类,如果要使用Callable来创建线程,则必须先使用Callable对象创建FutureTask类,然后作为对象传入Thread中
  6. FutureTask是RunnableFuture接口的实现类,既实现了Runnable接口来创建线程, 也实现了Future接口来实现异步任务,因为Runnable接口必须要重写方法,故没有无参构造,必须传入参数,要么传入Runnable接口实现类,要么传入Callable 接口实现类,当传入Callable 接口实现类时,此时就可以获得返回值
  7. Callable接口要重写call()方法,且必须指定返回值,并且call()会抛出异常
  8. 通过FutureTask创建线程后,此时使用 thread.start() 来启动线程并执行异步任务,然后使用 futureTask.get() 来获得异步任务的结果

3. Future优缺点

  1. Future可以实现异步任务;Runnable可以创建线程;Callable可以有返回值
  2. 使用FutureTask并用Callable实现类来创建,Callable接口实现时必须传入返回值类型,即泛型
  3. 直接使用FutureTask时每次都要创建一个实例对象来执行异步任务,且无法实现线程的复用,故效率和开销很大,此时可以使用线程池来复用线程
  4. 使用FutureTask和线程池异步多线程任务配合,此时只需要创建一个线程池来执行FutureTask的异步任务,而不用为每个FutureTask都创建一个Thread来执行
  5. FutureTask的get()容易造成阻塞,get()必须等待异步任务完成之后才可以得到返回结果
  6. 对于FutureTask任务,如果调用get()就会必须等待任务结束后才可以继续执行,会阻塞,故一般把get()放在最后再执行
  7. 可以在调用get()时传入一个超时时间,当超过该时间没返回时,就不会再继续等待,而是抛出异常
  8. 当调用get()时CPU会一直轮询判断get()是否完成,会一直轮询判断get()是否完成,可以在调用get()时传入超时时间,此时当超过超时时间未完成时会抛出异常, 不会一直阻塞
  9. 可以通过while循环内isDone()来判断异步任务是否完成,但轮询会耗费无所谓的CPU资源
  10. FutureTask获得结果时会阻塞,可以设置超时时间,但会抛出异常,也可以通过isDone()来判断,但会一直while循环,但轮询会一直占用CPU,故可以使用回调通知
  11. 回调通知、创建异步任务、多个任务前后依赖可以组合处理、对计算速度选最快

4. CompleteableFuture对Future的改进

  1. 回调通知、创建异步任务、多个任务前后依赖可以组合处理、对计算速度选最快
  2. CompletableFuture为什么会出现
    1. FutureTask执行异步任务时,如果调用get()则会一直阻塞,如果使用isDone()轮询的方式则会一直占用CPU
    2. 真正的异步任务是想通过回调的方式进行通知
    3. CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方,实现了回调通知当任务完成后执行回调函数
  3. CompletableFuture源码分析
    1. 实现了Future接口和CompletionStage接口
    2. CompletionStage接口:代表异步任务过程中的某一个阶段一个阶段完成之后可能会触发另外一个阶段,类似于Linux中的管道分隔符,一个阶段一个阶段的完成
    3. CompletableFuture 实现了Future的所有功能,并进行扩展,可能代表一个完成的Future,也可能代表一个完成阶段CompletionStage
    4. CompletableFuture实现了Future接口和CompletionStage接口
  4. 核心四个静态方法,来创建一个异步任务
    1. 通过静态方法来得到CompletableFuture对象
    2. runAsync无返回值,传入Runnable对象,可选Executor线程池
    3. supplyAsync有返回值,传入Supplier对象(Callable就是Supplier,有返回值),可选Executor线程池
    4. Executor线程池:如果没有指定Executor的方法直接使用默认ForkJoinPool.commonPool()作为线程池执行异步代码,此时默认使用ForkJoinPool的线程池来运行异步任务
    5. 使用静态方法创建好CompletableFuture对象后,
    6. Executor线程池使用Executors工具类可以创建三种类型(单个、定长、可变)的线程池但不推荐,因为内部的阻塞队列是MAX长的,此时一个自定义七个参数(核心线程个数、最大线程个数、阻塞队列、存活时间、线程工厂类、拒绝策略)来创建线程池对象Executor
  5. CompletableFuture回调通知,减少阻塞和轮询
    1. CompletableFuture是Future的功能增强版,减少了阻塞和轮询,其可以传入回调对象,当异步任务完成时,自动调用回调对象的回调方法
    2. 因为使用线程池来运行异步任务,故不使用start()就会自动运行线程池是先请求核心线程,当没有核心线程时则进入阻塞队列当阻塞队列满时看能不能新建线程,如果不可以则直接拒绝
    3. 创建 CompletableFuture 时,可以通过whenComplete((v,e)->{})来设置回调函数,此时当运行结束后就会调用设置的回调函数,此时CompletableFuture执行结束后会调用whenCompletable中传入的函数,且以(v,e)为参数,此时的v就是CompletableFuture返回结果,e为异常
    4. 也可以通过 .exceptionally(e->{}) 来对异常情况进行处理
    5. 没有传入Executor线程池时,此时会使用默认的ForkJoinPool,该线程池是守护线程, 当主线程结束后则会自动结束,故此时其内运行的线程也会结束,没有指定线程池时会使用ForkJoinPool线程池,该线程池是守护线程
    6. CompletableFuture可以实现回调通知,通过内部的静态方法来创建对象,然后使用.whenComplate()来创建回调函数,此时当运行结束后会自动调用其内的函数以(v,e)为参数,有返回值时,v就是返回的结果,然后可以使用 .exceptionally(e->{})来处理异常的情况
    7. completableFuture可以使用回调通知的方式来执行异步任务,通过静态方法来创建对象,可以指定线程池不指定时使用默认的ForkJoinPool线程池,此时是守护线程,当主线程结束后就会立即结束,使用线程池时会自动执行,不需要手动的start()
    8. CompletableFuture代表了异步任务的某一个阶段,代表了某一个阶段或者某一个异步任务,当一个阶段完成后可能会执行下一个阶段,通过回调通知的方式返回结果,即通过.whenComplete((v,e)->{}),当执行结束且没有异常时就会执行传入的函数,此时就可以对结果进行处理或者开启下一个阶段的任务
    9. CompletableFuture异步任务结束之后会自动回调某个对象的方法,即回调通知;主线程设置好回调函数后就不关心异步任务的执行,异步任务可以顺序执行;异步任务出错时,也会自动调用某个对象的方法

5. CompletableFuture大厂案例精讲

  1. 函数式编程
    1. 函数式接口
      1. Runnable无参数无返回值,run()函数
      2. Function:接收一个参数,有返回值apply()函数
      3. Consumer:消费性函数接口,接收一个参数,但是没有返回值accept()函数
      4. BiConsumer:接收两个参数,没有返回值,accept()函数
      5. Supplier:供给型函数接口,没有参数,有一个返回值get()函数
    2. Chain链式调用
      1. 必须保证每个函数的返回值是自己时才可以使用链式调用
      2. 如 T.setA().setB();,必须保证 T.setA()的返回值是T本身才可以实现链式调用
  2. **join()和get()对比
    1. ForkJoin分支合并,可以将一个大的任务拆分为多个子任务分别计算,然后使用join将结果合并
    2. join()和get()都可以获得异步任务的返回结果,但get()必须做异常处理,而join()不需要做异常处理
    3. join()和get()都可以获得异步任务的结果,但get()必须进行异常处理,而join()不需要进行异常处理
  3. 需求说明
    1. 先完成功能再完善性能!
    2. 顺序执行的任务使用异步任务去完成,对比顺序执行和异步执行的性能
  4. 业务实现
    1. 使用流式编程,将集合通过.stream()转换为流对象,然后对每个对象通过.map()映射为另一个对象,再通过.collect()转换为一个集合,其中可以映射为异步任务的对象集合,再将其结果重新映射
    2. 使用流式编程,先.stream()转换为流,然后map(Function)通过函数式编程将流的每一个对象进行处理,映射为新的对象,然后使用 .collect(Collectors.toList()) 将映射的结果重新变为集合
    3. 流式编程就是将一个数据流映射到另一个数据流,如将一个List<>映射到另一个List<>,对其内的每一个元素进行处理映射到另一个元素上
    4. 在流式编程时也可以使用CompletableFuture对每个对象进行异步任务处理,此时会映射为 List< CompletableFuture<> >,然后再.stream()转换为流,继续对每个元素使用 join() 得到返回值对象
    5. 从功能到性能

6. CompleteableFuture常用方法

  1. 通过静态方法来创建CompletableFuture对象有runAsync无返回值和supplyAsync有返回值两种,均可以传入Executor线程池,然后可以通过 .whenComplete(BiComsumer) 来处理执行完成的后续操作,也可以通过 .exectp…来处理异常
  2. CompletableFuture不仅实现了Future接口还实现了CompletionStage接口,故不仅可以是一个完整的异步任务,也可以是任务的某个阶段
  3. 获得结果和触发计算
    1. 获得结果可以通过get()和join(),也可以在get()中设置超时时间,超时会抛出异常,还可以通过 getNow(T)
    2. get()方法必须进行异常处理,join()不需要异常处理,都可以获得异步任务的处理结果,且可以在get()时指定超时时间
    3. 可以通过getNow()获得计算结果,此时要传入一个default缺省值表示如果此时获得时没有完成,则返回设置的缺省值,服务降级,没有计算完成时返回一个设置的默认值,不会抛出异常,在使用get()时会一直阻塞,且如果设置超时时间则会抛出异常,故此时可以使用getNow(default),当执行时如果没有执行结束则会返回缺省值,且不会抛出异常
    4. 主动触发计算可以通过 complete() 函数返回boolean类型complete(value)会打断get()阻塞,直接将value作为计算结果,就是说如果执行complete()时没有执行结束,则直接返回true,并将value作为计算结果否则返回false,计算结果不变
  4. 对计算结果进行处理
    1. thenApply计算结果存在依赖关系将两个线程串行化,在第一个异步任务完成后,调用thenApply(Function)将上一步的结果作为下一步的输入继续执行最后一步的返回值就是整个异步任务的返回值,因为前后步存在依赖关系,故如果当前步骤有异常则会叫停,当前步错不走下一步,前后存在依赖关系
    2. 此时会创建多个阶段来执行任务,最后一步任务的执行结果就是最后的返回结果
    3. handle将两个线程串行化,正常情况和thenApply一样,将上一步的返回结果作为参数传入下一步执行,但是出错后仍会继续向下走,带着错误参数向下走,此时传入的不再仅仅是上一步的结果(f),而是会带着错误参数(f,e),handle有异常也可以向下走,带着异常参数进行处理,handle也是将上一步的结果作为当前步的参数输入,并返回结果,但其出现错误时仍会继续运行, 且将错误信息作为参数返回,故此时handle传入的是BiFunction
  5. 对计算结果进行消费
    1. 对计算结果进行处理使用thenApply或者handle,都存在依赖关系,将两个线程串行化将上一步的结果作为下一步的参数进行传入并执行,一个出错后就不再执行,一个出错后将错误参数传入继续执行,故每一步都要返回一个结果,作为下一步的参数
    2. 对计算结果进行消费时,接收任务的处理结果,并消费处理无返回结果,传入Consumer函数式接口,使用thenAccept时传入参数,但没有返回值
    3. 对计算结果消费时,不进行返回,只进行消费
    4. 通过thenAccept()来实现对计算结果的消费(Consumer函数式接口通过accept()函数实现)
    5. thenRun(Runnable)不需要上一步的结果也没有返回thenApply(Function)需要上一步的结果,且有返回值thenAccept(Consumer)需要上一步的结果但没有返回值
    6. 且如果最后调用join()或者get()得到返回结果时,此时返回的是最后一步的返回值,如果最后一步是thenRun或者thenAccept,则此时就没有返回结果
  6. CompletableFuture和线程池
    1. 对于同一个CompletableFuture异步任务中的不同阶段如果不加线程池,则此时会使用同一个默认线程池中的线程串行执行
    2. 但如果第一步使用的自定义线程池,其后面没有Async时则会使用自定义线程池,但如果有Async则会创建默认线程池运行,但可能因为优化,使用Main线程;Async函数不可以再传入线程池
    3. 当前任务阶段使用和前一个任务阶段相同的线程,但如果当前任务使用thenXXXAsync异步执行,则此时就不再使用上一步的线程池
    4. CompletableFuture和线程池,同一个异步任务的不同任务阶段可能使用不同的线程,当前任务使用和上一个任务相同的线程,但如果当前任务使用Async,则会重新创建一个线程来执行,不会再使用上一个任务的线程
    5. 有可能因为处理太快系统优化切换原则直接使用main处理,有可能处理太快由于系统优化原则直接使用main处理
    6. 使用thenAsync且上一步使用自定义线程池时此时就会创建一个默认的ForkJoinPool来执行当前任务,会创建一个默认线程池,不会使用上一步的自定义线程池
    7. 使用Async函数创建一个新任务时,此时就会创建一个ForkJoinPool来执行当前任务
  7. 对计算速度进行选用
    1. 谁快用谁,对于多个异步任务,谁快就返回谁的结果
    2. A.applyToEither(B,BiFunction)来实现谁快就返回谁,其中A和B是使用CompletableFuture创建的异步任务Function是函数式接口,传入获胜者的计算结果,对其进行处理,并返回一个结果;且该函数也会返回一个CompletableFuture对象,并将Function的返回值作为返回结果,会等待二者执行完成后再判读
    3. 调用A.applyToEither(B,Function)时,会等待A和B均执行完毕后才会比较谁更快,会等待两个异步任务均执行完成后才会执行,并返回一个异步任务
  8. 对计算结果进行合并
    1. 两个CompletableStage任务都完成后最终把两个任务的结果一起交给thenCombine来处理,即将两个异步任务的结果合并处理前提是两个任务均执行完成,故先完成的等待另一个完成
    2. 先完成的等待其他分支任务
    3. A.thenCombine(B, BiFunction),将两个任务的结果传入BiFunction进行处理,并返回一个结果,且该函数也返回一个CompletableFuture对象,以thenCombine返回的结果作为该对象的计算结果返回**
    4. 必须等待两个任务均执行完成后才可以合并
  9. 总结
    1. 使用CompletableFuture的静态方法先创建一个任务,其可以是一个完整的Future任务,也可以是Future的一个阶段,使用thenXxx来串行执行后面的依赖任务,可以在其后面串行执行别的任务阶段,通过thenXxx来执行
    2. .runAsync是无返回值的异步任务传入Runnable实现类对象,.supplyAsync是有返回值的异步任务,传入Callable实现类对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值