线程池不香了,结构化并发才是王道

ExecutorService的不足

  • 当电商APP中需要展示用户信息首页时,服务端接口既要获取用户信息,也要获取用户的订单信息,最后组装数据返回给前端。
  • 针对这个需求,我们可以同时开两个线程,并行执行获取用户信息和获取订单信息两个任务,等到两个任务都执行完并拿到结果后才组装数据返回给前端。
  • 我们先定义获取用户信息任务:

线程池不香了,结构化并发才是王道_安全

  • 输出结果为:

线程池不香了,结构化并发才是王道_java_02

  • 此时,获取订单信息任务在执行1秒后会抛出空指针异常,所以获取订单信息任务肯定不会执行成功,关键问题是:获取用户信息任务会不会执行成功呢?它又该不该执行成功呢?
  • 给大家5秒钟思考一下…
  • 我们直接运行代码看结果:

线程池不香了,结构化并发才是王道_word_03

  • 获取订单信息确实抛了异常,但是获取用户信息确实也是执行了,但是此次执行其实在当前业务场景下是没有意义的,因为我们是同时需要用户信息和订单信息来整合数据的,也就是希望获取订单信息和获取用户信息这两个任务中有一个任务执行失败了,另外那个任务就不执行了
  • 那ExecutorService能不能做到呢?有小伙伴可能想到了ExecutorService中的两个API:
  • invokeAll(Collection<? extends Callable> tasks):执行给定的所有任务,所有任务都执行完才返回
  • invokeAny(Collection<? extends Callable> tasks):执行给定的所有任务,任意一个任务执行完就返回
  • 所以目前来说,ExecutorService是做不到的,这就需要结构化并发了(主角总算登场了~)。

结构化并发的强大

  • 我们先看一段结构化并发的Demo代码:

线程池不香了,结构化并发才是王道_java_04

  • 从代码结构上,好像和上面用ExecutorService好像差不多,但是我们不能把StructuredTaskScope理解为一个线程池,而应该把它理解为一个任务,一个StructuredTask,然后利用它fork了两个子任务,然后join等待两个子任务执行完,最后获取任务结果.
  • 另外,我们这里用的是ShutdownOnFailure,它还一个兄弟是ShutdownOnSuccess
  • ShutdownOnFailure:表示有一个子任务执行失败了,整个StructuredTask就直接结束,其他子任务也不会执行了(这不就是我们想要的嘛…)
  • ShutdownOnSuccess:表示有一个子任务执行成功了,整个StructuredTask就直接结束,其他子任务也不会执行了
  • 我们把两个子任务的代码稍作调整,获取订单信息任务为(执行1秒):

线程池不香了,结构化并发才是王道_安全_05

  • 如果把ShutdownOnFailure替换为ShutdownOnSuccess,大家猜一下会是什么情况?因为获取订单信息任务只需要执行1秒就成功了,所以当获取订单信息任务执行完后,整个StructuredTask就执行结束了,所以获取用户信息任务就不会执行了(只执行了开头,相当于任务中止了),输出结果为:

线程池不香了,结构化并发才是王道_java_06

  • 从中可以看出,ShutdownOnFailure和ShutdownOnSuccess就是结构化并发的精髓,那为什么叫结构化并发呢?我个人的理解是通过结构化并发,可以将多个并发子任务组合成一个大任务,并且这个大任务可以控制子任务的执行进度,就算子任务已经开始执行了也可以被中止.