根据摩尔定律(Moore’s law),集成电路晶体管的数量差不多每两年就会翻一倍。但是晶体管数量指数级的增长不一定会导致 CPU 性能的指数级增长。处理器制造商花了很多年来提高时钟频率和指令并行。在新一代的处理器上,单线程程序的执行速率确实有所提高。但是,时钟频率不可能无限制地提高,如处理器 AMD FX-9590 的时钟频率达到5 GHz,这已经非常困难了。如今处理器制造商更喜欢采用多核处理器(multi-core processors)。拥有4核的智能手机已经非常普遍,更不用提手提电脑和台式机。结果,软件不得不采用多线程的方式,以便能够更好的使用硬件。线程池可以帮助程序员更好地利用多核 CPU。
线程池
好的软件设计不建议手动创建和销毁线程。线程的创建和销毁是非常耗 CPU 和内存的,因为这需要 JVM 和操作系统的参与。64位 JVM 默认线程栈是大小1 MB。这就是为什么说在请求频繁时为每个小的请求创建线程是一种资源的浪费。线程池可以根据创建时选择的策略自动处理线程的生命周期。重点在于:在资源(如内存、CPU)充足的情况下,线程池没有明显的优势,否则没有线程池将导致服务器奔溃。有很多的理由可以解释为什么没有更多的资源。例如,在拒绝服务(denial-of-service)攻击时会引起的许多线程并行执行,从而导致线程饥饿(thread starvation)。除此之外,手动执行线程时,可能会因为异常导致线程死亡,程序员必须记得处理这种异常情况。
即使在你的应用中没有显式地使用线程池,但是像 Tomcat、Undertow这样的web服务器,都大量使用了线程池。所以了解线程池是如何工作的,怎样调整,对系统性能优化非常有帮助。
线程池可以很容易地通过 Executors 工厂方法来创建。JDK 中实现 ExecutorService 的类有:ForkJoinPool
ThreadPoolExecutor
ScheduledThreadPoolExecutor
这些类都实现了线程池的抽象。下面的一小段代码展示了 ExecutorService 的生命周期:public List> executeTasks(Collection> tasks) { // create an ExecutorService
// 创建 ExecutorService
final ExecutorService executorService = Executors.newSingleThreadExecutor(); // execute all tasks
// 执行所有任务
final List> executedTasks = executorService.invokeAll(tasks); // shutdown the ExecutorService after all tasks have completed
// 所有任务执行完后关闭 ExecutorService
executorService.shutdown(); return executedTasks;
}
首先,创建一个最简单的 ExecutorService —— 一个单线程的执行器(executor)。它用一个线程来处理所有的任务。当然,你也可以通过各种方式自定