27并发工具类-并发工具类模块热点问题答疑
前面我们用13篇文章的内容介绍了Java SDK提供的并发工具类,这些工具类都是久经考验的,所以学好用好它们对于解决并发问题非常重要。我们在介绍这些工具类的时候,重点介绍了这些工具类的产生背景、应用场景以及实现原理,目的就是让你在面对并发问题的时候,有思路,有办法。只有思路、办法有了,才谈得上开始动手解决问题。
前面我们用13篇文章的内容介绍了Java SDK提供的并发工具类,这些工具类都是久经考验的,所以学好用好它们对于解决并发问题非常重要。我们在介绍这些工具类的时候,重点介绍了这些工具类的产生背景、应用场景以及实现原理,目的就是让你在面对并发问题的时候,有思路,有办法。只有思路、办法有了,才谈得上开始动手解决问题。
前面几篇文章我们介绍了线程池、Future、CompletableFuture和CompletionService,仔细观察你会发现这些工具类都是在帮助我们站在任务的视角来解决并发问题,而不是让我们纠缠在线程之间如何协作的细节上(比如线程之间如何实现等待、通知等)。对于简单的并行任务,你可以通过“线程池+Future”的方案来解决;如果任务之间有聚合关系,无论是AND聚合还是OR聚合,都可以通过CompletableFuture来解决;而批量的并行任务,则可以通过CompletionService来解决。
在《23 | Future:如何用多线程实现最优的“烧水泡茶”程序?》的最后,我给你留了道思考题,如何优化一个询价应用的核心代码?如果采用“ThreadPoolExecutor+Future”的方案,你的优化结果很可能是下面示例代码这样:用三个线程异步执行询价,通过三次调用Future的get()方法获取询价结果,之后将询价结果保存在数据库中。
前面我们不止一次提到,用多线程优化性能,其实不过就是将串行操作变成并行操作。如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化,例如下面的示例代码,现在是串行的,为了提升性能,我们得把它们并行化,那具体实施起来该怎么做呢?
在上一篇文章《22 | Executor与线程池:如何创建正确的线程池?》中,我们详细介绍了如何创建正确的线程池,那创建完线程池,我们该如何使用呢?在上一篇文章中,我们仅仅介绍了ThreadPoolExecutor的 void execute(Runnable command) 方法,利用这个方法虽然可以提交任务,但是却没有办法获取任务的执行结果(execute()方法没有返回值)。而很多场景下,我们又都是需要获取任务的执行结果的。那ThreadPoolExecutor是否提供了相关功能呢?必须的,这么重要的功能当然需要提供了。
虽然在Java语言中创建线程看上去就像创建一个对象一样简单,只需要new Thread()就可以了,但实际上创建线程远不是创建一个对象那么简单。创建对象,仅仅是在JVM的堆里分配一块内存而已;而创建一个线程,却需要调用操作系统内核的API,然后操作系统要为线程分配一系列的资源,这个成本就很高了,所以线程是一个重量级的对象,应该避免频繁创建和销毁。
那如何避免呢?应对方案估计你已经知道了,那就是线程池。
前面我们多次提到一个累加器的例子,示例代码如下。在这个例子中,add10K()这个方法不是线程安全的,问题就出在变量count的可见性和count+=1的原子性上。可见性问题可以用volatile来解决,而原子性问题我们前面一直都是采用的互斥锁方案。
Java并发包有很大一部分内容都是关于并发容器的,因此学习和搞懂这部分的内容很有必要。
Java 1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差,而Java 1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富了。下面我们就对比二者来学习这部分的内容。
前几天老板突然匆匆忙忙过来,说对账系统最近越来越慢了,能不能快速优化一下。我了解了对账系统的业务后,发现还是挺简单的,用户通过在线商城下单,会生成电子订单,保存在订单库;之后物流会生成派送单给用户发货,派送单保存在派送单库。为了防止漏派送或者重复派送,对账系统每天还会校验是否存在异常订单。