Java线程池介绍

根据摩尔定律(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 的生命周期:

 

 1   public List<Future<T>> executeTasks(Collection<Callable<T>> tasks) {
 2 
 3         // create an ExecutorService
 4         // 创建 ExecutorService
 5         final ExecutorService executorService = Executors.newSingleThreadExecutor();
 6 
 7         // execute all tasks
 8         // 执行所有任务
 9         final List<Future<T>> executedTasks = executorService.invokeAll(tasks);
10 
11         // shutdown the ExecutorService after all tasks have completed
12         // 所有任务执行完后关闭 ExecutorService
13         executorService.shutdown();
14 
15         return executedTasks;
16 
17     }

 

 

首先,创建一个最简单的 ExecutorService —— 一个单线程的执行器(executor)。它用一个线程来处理所有的任务。当然,你也可以通过各种方式自定义 ExecutorService,或者使用 Executors 类的工程方法来创建 ExecutorService:

 

newCachedThreadPool() :创建一个 ExecutorService,该 ExecutorService 根据需要来创建线程,可以重复利用已存在的线程来执行任务。

 

newFixedThreadPool(int numberOfThreads) :创建一个可重复使用的、固定线程数量的 ExecutorService。

 

newScheduledThreadPool(int corePoolSize):根据时间计划,延迟给定时间后创建 ExecutorService(或者周期性地创建 ExecutorService)。

 

newSingleThreadExecutor():创建单个工作线程 ExecutorService。

 

newSingleThreadScheduledExecutor():根据时间计划延迟创建单个工作线程 ExecutorService(或者周期性的创建)。

 

newWorkStealingPool():创建一个拥有多个任务队列(以便减少连接数)的 ExecutorService。

 

在上面这个例子里,所有的任务都只执行一次,你也可以使用其他方法来执行任务:

 

  • void execute(Runnable)

  • Future submit(Callable)

  • Future submit(Runnable)

 

最后,关闭 executorService。Shutdown() 是一个非阻塞式方法。调用该方法后,ExecutorService 进入“关闭模式(shutdown mode)”,在该模式下,之前提交的任务都会执行完成,但是不会接收新的任务。如果想要等待任务执行完成,需要调用 awaitTermination() 方法。

 

ExecutorService 是一个非常有用的工具,可以帮助我们很方便执行所有的任务。它的好处在什么地方呢?我们不需要手动创建工作线程。一个工作线程就是 ExecutorService 内部使用的线程。值得注意的是,ExecutorService 管理线程的生命周期。它可以在负载增加的时候增加工作线程。另一方面,在一定周期内,它也可以减少空闲的线程。当我们使用线程池的时候,我们就不再需要考虑线程本身。我们只需要考虑异步处理的任务。此外,当出现不可预期的异常时,我们不再需要重复创建线程,我们也不需要担心当一个线程执行完任务后的重复使用问题。最后,一个任务提交以后,我们可以获取一个未来结果的抽象——Future。当然,在 Java 8中,我们可以使用更优秀的 CompletableFuture,如何将一个 Future 转换为 CompletableFuture 已超出了本文所讨论的范围。但是请记住,只有提交的任务是一个 Callable 时,Future 才有意义,因为 Callable 有输出结果,而 Runnable 没有。

 

内部组成

 

每个线程池由几个模块组成:

 

  • 一个任务队列,

  • 一个工作线程的集合,

  • 一个线程工厂,

  • 管理线程状态的元数据。

 

ExecutorService 接口有很多实现,我们重点关注一下最常用的 ThreadPoolExecutor。实际上,newCachedThreadPool()、newFixedThreadPool() 和 newSingleThreadExecutor() 三个方法返回的都是 ThreadPoolExecutor 类的实例。如果要手动创建一个ThreadPoolExecutor 类的实例,至少需要5个参数:

 

  • int corePoolSize:线程池保存的线程数量。

  • int maximumPoolSize:线程的最大数量。

  • long keepAlive and TimeUnit unit:超出 corePoolSize 大小后,线程空闲的时间到达给定时间后将会关闭。

  • BlockingQueue workQueue:提交的任务将被放置在该队列中等待执行。

  • thread-pool

 

 

阻塞队列

 

LinkedBlockingQueue 是调用 Executors 类中的方法生成 ThreadPoolExecutor 实例时使用的默认队列,PriorityBlockingQueue 实际上也是一个BlockingQueue,不过,根据设定的优先级来处理任务也是一个棘手的问题。首先,提交一个 Runnable 或 Callable 任务,该任务被包装成一个 RunnableFuture,然后添加到队列中,ProrityBlockingQueue 比较每个对象来决定执行的优先权(比较对象是包装后的RunnableFuture而不是任务的内容)。不仅如此,当 corePoolSize 大于1并且工作线程空闲时,ThreadPoolExecutor 可能会根据插入顺序来执行,而不是 PriorityBlockingQueue 所期望的优先级顺序。

 

默认情况下,ThreadPoolExecutor 的工作队列(workQueue)是没有边界的。通常这是没问题的,但是请记住,没有边界的工作队列可能导致应用出现内存溢出(out of memory)错误。如果要限制任务队列的大小,可以设置 RejectionExecutionHandler。你可以自定义处理器或者从4个已有处理器(默认AbortPolicy)中选择一个:

 

  • CallerRunsPolicy

  • AbortPolicy

  • DiscardPolicy

  • DiscardOldestPolicy

 

线程工厂

 

线程工厂通常用于创建自定义的线程。例如,你可以增加自定义的 Thread.UncaughtExceptionHandler 或者设置线程名称。在下面的例子中,使用线程名称和线程的序号来记录未捕获的异常。

 

 1 public class LoggingThreadFactory implements ThreadFactory {
 2 
 3     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 4     private static final String THREAD_NAME_PREFIX = "worker-thread-";
 5     private final AtomicInteger threadCreationCounter = new AtomicInteger();
 6 
 7     @Override
 8     public Thread newThread(Runnable task) {
 9 
10         int threadNumber = threadCreationCounter.incrementAndGet();
11         Thread workerThread = new Thread(task, THREAD_NAME_PREFIX + threadNumber);
12     
13         workerThread.setUncaughtExceptionHandler(thread, throwable -> logger.error("Thread {} {}", thread.getName(), throwable));
14     
15         return workerThread;
16 
17     }
18 }

 

 

生产者消费者实例

 

生产者消费者是一种常见的同步多线程处理问题。在这个例子中,我们使用 ExecutorService 解决此问题。但是,这不是解决该问题的教科书例子。我们的目标是演示线程池来处理所有的同步问题,从而程序员可以集中精力去实现业务逻辑。

 

Producer 定期的从数据库获取新的数据来创建任务,并将任务提交给 ExecutorService。ExecutorService 管理的线程池中的一个工作线程代表一个 Consumer,用于处理业务任务(如计算价格并返回给客户)。

 

首先,我们使用 Spring 来配置:

 

 1 @Configuration
 2 public class ProducerConsumerConfiguration {
 3 
 4     @Bean
 5     public ExecutorService executorService() {
 6 
 7         // single consumer
 8         return Executors.newSingleThreadExecutor();
 9     }
10 
11     // other beans such as a data source, a scheduler, etc.
12 
13 }

 

 

然后,建立一个 Consumer 及一个 ConsumerFactory。该工程方法通过生产者调用来创建一个任务,在未来的某一个时间点,会有一个工作线程执行该任务。

 

 1 public class Consumer implements Runnable {
 2 
 3     private final BusinessTask businessTask;
 4     private final BusinessLogic businessLogic;
 5 
 6     public Consumer(BusinessTask businessTask, BusinessLogic businessLogic) {
 7 
 8         this.businessTask = businessTask;
 9         this.businessLogic = businessLogic;
10 
11     }
12 
13     @Override
14     public void run() {
15 
16         businessLogic.processTask(businessTask);
17     }
18 
19 }
20 
21 @Component
22 public class ConsumerFactory {
23 
24     private final BusinessLogic businessLogic;
25 
26     public ConsumerFactory(BusinessLogic businessLogic) {
27         this.businessLogic = businessLogic;
28     }
29 
30     public Consumer newConsumer(BusinessTask businessTask) {
31         return new Consumer(businessTask, businessLogic);
32     }
33 
34 }

 

 

最后,有一个 Producer 类,用于从数据库中获取数据并创建业务任务。在这个例子中,我们假定 fetchData() 是通过 scheduler 周期性调用的。

 

 1 @Component
 2 public class Producer {
 3 
 4     private final DataRepository dataRepository;
 5     private final ExecutorService executorService;
 6     private final ConsumerFactory consumerFactory;
 7 
 8     @Autowired
 9     public Producer(DataRepository dataRepository, ExecutorService executorService,
10 
11     ConsumerFactory consumerFactory) {
12 
13         this.dataRepository = dataRepository;
14         this.executorService = executorService;
15         this.consumerFactory = consumerFactory;
16 
17     }
18 
19     public void fetchAndSubmitForProcessing() {
20 
21         List<Data> data = dataRepository.fetchNew();
22     
23         data.stream()
24         // create a business task from data fetched from the database
25         .map(BusinessTask::fromData)
26         // create a consumer for each business task
27         .map(consumerFactory::newConsumer)
28         // submit the task for further processing in the future (submit is a non-blocking method)
29         .forEach(executorService::submit);
30 
31     }
32 }

 

 

非常感谢 ExecutorService,这样我们就可以集中精力实现业务逻辑,我们不需要担心同步问题。上面的演示代码只用了一个生产者和一个消费者。但是,很容易扩展为多个生产者和多个消费者的情况。

 

总结

 

JDK 5 诞生于2004年,提供很多有用的并发工具,ExecutorService 类就是其中的一个。线程池通常应用于服务器的底层(如 Tomcat 和 Undertow)。当然,线程池也不仅仅局限于服务器环境。在任何密集并行(embarrassingly parallel)难题中它们都非常有用。由于现在越来越多的软件运行于多核系统上,线程池就更值得关注了。

转载于:https://www.cnblogs.com/kangye1014/p/4993961.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cocoa pods的安装与我遇到的问题

1.打开终端 终端输入 ruby -v 查看ruby的版本 打印代码&#xff1a; ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15] 2. 更换ruby镜像 终端输入如下命令&#xff08;把Ruby镜像指向taobao&#xff0c;避免被墙&#xff0c;你懂得&#xff09; a.移…

Implicit declaration of function 'NSFileTypeForHFSTypeCode' is invalid in C99

一般出现该问题是因为通过C调用了unix/linux 底层接口&#xff0c;所以需要调整c语言的编译选项&#xff0c;设置方法见下图&#xff1a;(根据实际情况选择相应的编译选项)

DebugView 调试入门

参考链接&#xff1a;http://blog.csdn.net/jiankunking/article/details/44984487 软件下载地址&#xff1a;点击打开链接 debugview 可以捕获程序中由TRACE(debug版本)和OutputDebugString输出的信息。支持Debug、Release模式编译的程序&#xff08;即该软件捕获的是exe直接运…

DebugView使用笔记

1. 什么是DebugView? 它是Sysinternals公司的系列调试工具。可以捕获程序中由TRACE()和OutputDebugString输出的信息。 2. C需要完成哪些工作呢&#xff1f; 将打印的信息用OutputDebugString输出&#xff0c;示例&#xff1a; [cpp] view plaincopy #include "stdio.h&q…

QT之深入理解QThread

QT之深入理解QThread 理解QThread之前需要了解下QThread类&#xff0c;QThread拥有的资源如下&#xff08;摘录于QT 5.1 帮助文档&#xff09;&#xff1a;在以上资源中&#xff0c;本文重点关注槽&#xff1a;start()&#xff1b;信号&#xff1a;started()、finished()&#…

springMVC数据封装成POJO

springMVC把前台的数据封装为POJO与struts2的封装形式不同。struts2需要在控制器声明需封装的POJO&#xff0c;而springMVC不需要任何准备工作&#xff0c;只需在相应的方法的参数中加上需封装的POJO&#xff0c;当用户提交表单时&#xff0c;springMVC会根据表单中dom元素的na…

10.11 安装pod

原文地址&#xff1a;http://www.jianshu.com/p/5fc15906c53a 感谢。 更新升级10.11 cocoapods安装出问题最简单的解决方法 这是因为10.11把cocoapods直接干掉了 sudo gem install -n /usr/local/bin cocoapods 再加一句&#xff0c;完美解决 sudo xcode-select --switch /App…

JavaScript中的原型继承原理

在JavaScript当中&#xff0c;对象A如果要继承对象B的属性和方法&#xff0c;那么只要将对象B放到对象A的原型链上即可。而某个对象的原型链&#xff0c;就是由该对象开始&#xff0c;通过__proto__属性连接起来的一串对象。__proto__属性是JavaScript对象中的内部属性&#xf…

建模元件有哪些在MapleSim中

信号库&#xff1a;包含通用信号模块、布尔、控制器、离散信号模块、信号源、线性信号模块、非线性信号模块、时间离散信号模块、查询表、信号转换器、数学运算、关系元件、特殊信号模块&#xff0c;应用案例。 电子库&#xff1a;包含电阻、运算放大器、二极管、步进电机、模拟…

【C++】VS2010将写好的程序打包成安装文件发布

参考链接&#xff1a;http://blog.csdn.net/yongh701/article/details/51326142 我们可以将自己写好的VS2010程序打包成安装文件&#xff0c;给用户安装&#xff0c;具体步骤如下&#xff1a; 1、如下图&#xff0c;同样是新建一个项目&#xff0c;但是这次是新建一个其它项目…

01_jeecms建站

一、环境安装 JDK5TOMCAT5.5MYSQL5及以上http://www.jeecms.com/tutorial/index.jhtml参考环境安装篇二、解压文件安装包jeecms-v5zip,如图图1ROOT文件夹复制放到tomcat下的webapps文件夹&#xff08;注&#xff1a;请先删除webapps下原有的默认ROOT文件夹&#xff09;如不想部…

WiFi基本知识

转自&#xff1a;http://blog.csdn.net/myarrow/article/details/7930131 1. IE802.11简介 标准号IEEE 802.11bIEEE 802.11aIEEE 802.11gIEEE 802.11n标准发布时间1999年9月1999年9月2003年6月2009年9月工作频率范围2.4&#xff0d;2.4835GHz 5.150&#xff0d;5.350GHz5.475&a…

libev 宏展开

想看源码&#xff0c;宏太多&#xff0c;看着累&#xff0c;宏展开&#xff0c;再看&#xff0c;功力时间不够&#xff0c;先放下 放上宏展开后的代码。 libev4.20 展开方示为 ./configure 修改makefile文件&#xff0c;字符串 替换CC为 CPP 注意要把基础的CC定义保留 make mv …

FreeRTOS高级篇7---FreeRTOS内存管理分析

原文&#xff1a;http://blog.csdn.net/zhzht19861011/article/details/51606068 内存管理对应用程序和操作系统来说都非常重要。现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关。 FreeRTOS操作系统将内核与内存管理分开实现&#xff0c;操作系统内核仅规定了必要的内…

FreeRTOS学习笔记——互斥型信号量

来自&#xff1a;http://blog.csdn.net/xukai871105/article/details/43456985 0.前言 在嵌入式操作系统中互斥型信号量是任务间资源保护的重要手段。下面结合一个具体例子说明FreeRTOS中的互斥型信号量如何使用。 【相关博文】 【FreeRTOS STM32移植笔记】 【FreeRTOS学习笔记…

FreeRTOS系列第19篇---FreeRTOS信号量

来自&#xff1a;http://blog.csdn.net/zhzht19861011/article/details/50835613 本文介绍信号量的基础知识&#xff0c;详细源码分析见《FreeRTOS高级篇6---FreeRTOS信号量分析》 1.信号量简介 FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量&#xff08;以后简称…

蓝牙HCI剖析(一)

来自&#xff1a;http://blog.csdn.net/xiaoxiaopengbo/article/details/51334257 一.HCI介绍 HCI提供了访问bluetooth control的统一接口&#xff0c;通俗来讲&#xff0c;就是定义了特定的格式来控制蓝牙芯片来做相应的动作&#xff08;比如inquiry,connect,disconnect&#…

ASP.NET状缓存Cache的应用-提高数据库读取速度

ASP.NET状缓存Cache的应用-提高数据库读取速度 原文:ASP.NET状缓存Cache的应用-提高数据库读取速度一、 Cache概述 既然缓存中的数据其实是来自数据库的&#xff0c;那么缓存中的数据如何和数据库进行同步呢&#xff1f;一般来说&#xff0c;缓存中应该存放改动不大或者对…

入门级----测试的执行、环境的搭建、每日构建、测试记录和跟踪、回归测试、测试总结和报告...

测试用例的准备&#xff0c;都是为了执行测试准备的。 测试环境的搭建 &#xff08;1&#xff09;测试数据&#xff1a;有些测试需要使用大批量的数据&#xff0c;例如容量测试、压力测试等。根据产品的具体测试要求&#xff0c;可能需要在数据库表插入大量的数据&#xff0c;准…

限制MySQL Binlog的传输速率

最近一台核心库备库完成恢复后打开slave&#xff0c;导致主库传送binlog&#xff0c;瞬间占满网络&#xff0c;触发故障。 为了做一些限制&#xff0c; 给mysql在发送binlog的函数(mysql_binlog_send)里每隔一段时间sleep一次&#xff0c; 增加了两个参数&#xff1a; master_s…