1了解概念:什么是线程池
Thread pool线程池,顾名思义就是由若干个线程“Thread类型对象”所组成的一个"池",但是切勿只认为Thread pool(线程池)内部就仅仅只有Thread类型对象(线程),这样理解是不够准确的。因为Thread pool内还有"任务队列",任务队列:Queue
——————————————————————
2线程池的内部构造
一个线程池ThreadPool类型对象,都会用其内部的两个集合组件:一个是Thread[] threads(存放线程的地方)(线程池,线程集合,线程数组),一个是Queue
——————————————————————
3线程池的工作理念
线程池中的线程(Thread类型实例)可以运行执行工作队列(任务队列)(Queue
——————————————————————
4使用线程池的好处
在使用线程池之前我们必须要关注到thread的创建,控制,与thread之间的通信问题.使用了thread pool(线程池)之后,程序员只要专注实现要执行的任务(Runable类型实现类的实现)的构造即可(专心实现核心业务关注点程序).使用Executor框架,能让程序员把精力和焦点放在多线程程序要实现的业务逻辑功能上而不是写出一滩围绕线程的如何创建和管理的(与线程thread本身的工作机制有关的)一系列的非核心业务关注点的代码,让你的心思与精力完全放在核心业务关注点上来.
————————————————————————
5线程池(Thread pool)与吞吐量问题 (测试结果才是说明问题的直接证据)
thread可能会被Block住的事实意味着我们必须要有比CPU更多的thread在pool里面(这是Thread pool中的thread会被block的原因之一),由此得出的推论:多个Cpu密集的线程处理方案要比一个CPU处理多线程的吞吐量要好,即一个cpu密集的多thread处理可以得到最佳的吞吐量。例如每一个thread有50%的时间被block住,你会希望每个Cpu要处理两个thread,如果每个thread有66%被阻断的时间(执行总时间的66%)换句话说每个thread在完成它的任务所需要的总时间的33%是有效时间,其余为该线程被阻断的时间,那么你会希望每个CPU能够总共处理三个thread,如此类推,以达到最佳的程序性能吞吐量。当然,你不太可能让程序模型设计到如此程度的详细细节,且任何模型一旦计算到随机出现的尖峰流量更是难以估计.最后,你需要进行一些测试来测定thread pool的适当大小.但如果CPU资源缺乏时,调节thread的数目(但还是要保持CPU的利用率)会增加应用程序的吞吐量.
————————————————————————
6什么情况下不要使用线程池:
从程序功能的需求与设计的目的来讲:
如果你的程序是用来进行批处理,或只是提供单一回答或报告,是否用多线程(多个单独分离的thread)或者使用线程池(thread pool)就一点都不重要.因为这种情况下使用线程者并不关心某个thread(线程)是否能比其他的thread(线程)先完成,即没有线程排队的概念要求.注意:这并不代表你可以期待创建数千个thread而不会有事:thread是需要内存的,而使用越多的内存就会对系统性能产生更大的冲击.此外,当操作系统管理数千个thread而不是只有几个thread的时候是需要成本的.另外,如果你的程序设计很漂亮地分离出了多个单独的thread且你只在乎所有这些thread的最终执行结果,而不在乎他们的排队与调度序列的时候,这时候也是不需要使用线程池(thread pool)的.
从程序的性能方面和吞吐量方面来讲:
thread pool 同样在可用的CPU资源足够来处理程序所需的所有工作任务时是不必要使用线程池的即这种情况下使用线程池是多此一举,因为线程池的使用目的就是为了处理现有Cpu数目少而thread数目多的情况,增加程序的吞吐量的。事实上,如果你的cpu数目较多且数据处理,计算速度超快,且足以应付多个单独线程(不再一个线程池中的多个独立线程)的处理那么在这种情况下你还画蛇添足的使用线程池是不明智地,这可能反而会影响系统的性能.举个例子:如果你的系统有八个CPU且你的线程池(thread pool)中只有四个thread(这样的情况下CPU们肯定吃不饱),task(任务,runnable类型实例化对象)甚至出现了在还有四个CPU闲置的情况下,该任务(task:Runnable类型实例)还要等待线程池提供(调度)有可以让该任务(Runnable类型实例)寄生到的thread(Thread类型对象,线程)被调度,这样使用线程池(thread pool)就太浪费CPU了,也达不到程序的处理任务的吞吐量的需求了.所以使用线程池(thread pool)的时候你需要调节线程池内thread的数量(Thread类型对象的数量)总数以让这些thread们不会淹没掉系统(拖累系统,过多过少都不好,过多则吃内存,过少则CPU吃不饱即线程池内没有足够数量的Thread类型对象被CPU调度来接手至所需要运行的任务(runnable类型实例)上),但是不管何种情况下你总不会希望你的thread数量比CPU数目还少吧,这样CPU会被饿死(闲置,浪费了计算能力与系统资源)(权威)
——————————————————————
7如何使用线程池
你要想使用Thread pool你就必须得做两件事:
第一:创建用于存放Runnable类型实例(任务)的任务队列Queue
第二:创建pool本身:即一个用于存放Thread类型实例(线程)的一个集合 Collection
__________________________________________________________________________________________________
线程池在JAVA语言中的一个成熟的封装实现类是java.util.concurrent.ThreadPoolExecutor这个类,它实现了ExecutorService这个接口(是个线程池理念中的成熟接口),它是ExecutorService这个接口的一个成熟的实现类.
定义如下:
package java.util.concurrent;
public class ThreadPoolExecutor implements ExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
public boolean prestartCoreThread( );
public int prestartAllCoreThreads( );
public void setMaximumPoolSize(int maximumPoolSize);
public int getMaximumPoolSize( );
public void setCorePoolSize(int corePoolSize);
public int getCorePoolSize( );
public int getPoolSize( );
public int getLargestPoolSize( );
public int getActiveCount( );
public BlockingQueue
public long getTaskCount( );
public long getCompletedTaskCount( );
}
这个类的使用说明:
corePoolSize:指定线程池thread pool中 最精瑞线程(thread)数量,最核心的线程数量,应保持最少的线程(Thread类型实例对象)数量,核心处理业务线程数(应必须(满足)达到的最基本的水平)
maximumPoolSize:指定线程池thread pool中 可保持的最多线程(thread)数量,最大线程数量,应保持最满时的线程(Thread类型实例对象)数量(最大极限)
以上的这两个参数会使得线程池中现有的Thread类型实例化对象的数目会在[corePoolSize maximumPoolSize]区间内变化.
若在ThreadPoolExecutor 的构造函数中指定corePoolSize的取值和maximumPoolSize的取值相同,则该线程池(这里指ThreadPoolExecutor 类型实例对象)的pool中的线程数量始终会保持不变,且就是核心处理业务线程数!
若如果对corePoolSize的取值和maximumPoolSize的取值不相同,那么线程池(这里指ThreadPoolExecutor 类型实例对象)会动态调整期内部pool中的线程(Thread类型实例)的数量,使得该数量值落在corePoolSize的取值和maximumPoolSize的取值的区间之内.注意:线程池中的目前(当前)的线程数量用ThreadPoolExecutor 类型实例对象的getPoolSize()这个方法可以返回得到!
关于BlockingQueue
___________________________________________________________________________________________________________________________________________________
那么上文提到的workQueue(存放任务Runnable实例的地方)和线程池(这里指ThreadPoolExecutor类型实例)中的pool(存放thread的地方)是如何交互的呢?pool中的thread又是怎样接到分派给它的Runnable类型实例(任务)的呢?下文回答这两个问题!
首先你要明确线程池(ThreadPoolExecutor实例)的构造是用指定的corePoolSize=min与 maximumPoolSize=max这两个构造参数构造出来的,min指定了pool中最少要保持的线程thread的数量,max指定了pool中最多应保持的thread数量,当一个Runnable类型实例(任务)要交由线程池去处理(希望委托给线程池中pool区域内的thread(Thread类型对象)去处理)时(通过ThreadPoolExecutor实例的execute(Runnable xxx)方法来实现这一步骤),会发生以下五种情况中的一种。
第二:如果pool中目前所创建的线程数量为n且min 第四:如果pool中目前所创建的线程数量为n且min 第五:如果pool中目前所创建的线程数量为n且min 其次:当你设置的任务队列(工作队列)BlockingQueue 经验之谈:使用线程池的最佳策略:如下 使用线程池时,在初始化阶段要设定BlockingQueue 在这一章中我们先探索讨论executor接口,它将多线程程序设计的细节对开发者隐藏了起来,它可以用来处理Runnable类型的任务,它是处理多Runnable类型对象(任务)的理想工具. java.util.concurrent.ThreadPoolExecutor这个类是对executor接口的良好实现,使用这个工具类可以在实现“线程池”效应(简化多线程程序的设计,加强程序的吞吐量使得多个任务被串行化并发处理,这里的串行化指得是任务等待处理是采用"排队等待的策略“,排队等待处理模式:遵循着FIFO原则排队)的好处外,可用其内的pool这个机制动态调节"池"中的线程数量,从而减少多线程对CPU的竞争,并能够让CPU密集的多线程程序更快地完成个别任务(Runnable类型任务). java线程池被认为被使用的最重要的原因是:重复利用已经被创建出来的thread要比不断地新创建thread在效率与资源开销上都要好得多!另一个原因是:你会发现其实线程池的好处关键在于:当CPU资源竞争较少的时候(比如说需要并发执行的thread数量较少时),每个需要处理的任务(Runnable类型实例的run()方法)完成的平均时间要比其它方式(非线程池方式这里指的是盲目地创建多个独立的处理任务用的线程的方式)要少! 有效地使用java线程池的关键在于选择正确地pool容量(pool中可持有的thread总数),与workQueue实现所使用的Queue 使用的案例代码如下: package javathreads.examples.ch10.example1; import java.util.concurrent.*; import javathreads.examples.ch10.*; public class ThreadPoolTest { public static void main(String[] args) { int nTasks = Integer.parseInt(args[0]); long n = Long.parseLong(args[1]); int tpSize = Integer.parseInt(args[2]); ThreadPoolExecutor tpe = new ThreadPoolExecutor( tpSize, tpSize, 50000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue Task[] tasks = new Task[nTasks]; for (int i = 0; i < nTasks; i++) { tasks[i] = new Task(n, "Task " + i); tpe.execute(tasks[i]); } tpe.shutdown( ); } } note that task is instance of Task,The class Task in this example is one of the implementations for Runnable (Runnable is a interface of java)
第一:
如果pool中目前所创建的线程数量
第三:如果pool中目前所创建的线程数量为n且min
总结:
当你在线程池机制中,你设置的任务队列(工作队列)BlockingQueue
答案:当任务队列的容量不加限制,会导致Runnable类型对象加入到workQueue中一定会成功,使得处理不过来的任务放到任务队列中去排队一定会成功,当待处理的任务爆膨的时候(这里指当pool中线程全忙没有空闲的闲置thread时),这些处理不过来的任务会排队(线程池中pool的线程全忙时新到来的没有被分配到线程的Runnable类型实例就会排队即被放到等待队列当中,当新的任务(Runnable型实例)越多时,排的队就越长,(要放到workQueue中的任务就要越多),这代表了pool永远不会创建比min还多的线程(因为处理不过来的任务会自动地排队,你就排去吧,没人搭理你,队伍排的长长的,而且pool不会单独创建新的线程去处理那些排不上队的Runnable类型任务,因为这种情况下根本不存在排不上队的任务了.篇外话:线程池的设计中pool只会为排不上队即放不进workQueue中去的Runnable类型实例任务创建新的线程Thread,以使得n值向max值趋近!这里的n指的是pool中的当前线程总数,max指的是初始化线程池时指定的线程池pool中限定的最多(大)线程总数,最大容量)(换句话说在workQueue的容量不做限定的这种情况下根本不存在排不了队的任务,即任务若要被放进任务队列中则一定会成功,因为任务队列容量无上限有的是地方,队伍排得越长,后续的排队的任务就越难被执行,因为前面排队的人实在太多了(且后排在后面的任务其优先被执行权不够(就越靠后)(这是由队列的FIFO的特性所决定的),就像排队搓澡一样,前面排的人越多,后面要搓澡的人等的时间就得越长)).
__________________________________________________________________________________________________________________________________
注意:使用固定容量值的workQueue+固定容量的pool这一策略的线程池的好处是:如果要执行的任务数比可分配与可调度的闲置thread数目要大的时候(即任务到来比处理的速度要快时),“过多的处理不过来的任务”就要进workQueue排队等待(按照fifo原则排队等待pool调度稍后有可闲置状态的thread去接受并处理它),当pool中的线程数量已到达上限max,且workQueue中的Runnable类型任务数量已到达p时,且pool中再无闲置thread(所有的thread全忙)时,那么继续到来的任务就能被拒绝处理,被抛弃,这样可以减少程序的负担,即减少线程池的可处理负担,提高了多线程程序的吞吐量。切记:没有魔术(梦之规则)(黄金规则)规则可以用来决定最佳容量大小的workQueue与pool容量的规则取值,可以遵循的道理与原则是:使用与CPU数量相同的thread数目,一般不会犯毛病,对于高要求与更复杂的多线程并发程序的设计,挑选pool容量值(线程池中线程数量的设置)的大小要由测试不同的值来确定线程池工作地最佳性能配置值!
_______________________________________________________________________________________________
本章总结:
executor接口所提供的模型,能够让你所对于要处理的多个任务,描述并规理成串行化的多个任务的概念.使得你对多任务的串行化处理有了个理想的实现方案与工具.程序员可以只关注程序的逻辑需求,而不用烦恼于关于java线程(thread)如何被创建与使用(调度)的诸多细节.就能写出高效有用的多线程并发程序(或者说多任务处理程序).
——————————————————————————————————————
In this example, we're using the tasks to calculate Fibonacci numbers as we do in Chapter 9. Once the pool is constructed, we simply add the tasks to it (using the execute() method). When we're done, we gracefully shut down the pool; the existing tasks run to completion, and then all the existing threads exit. As you can see, using the thread pool is quite simple, but the behavior of the pool can be complex depending on the arguments used to construct it. We'll look into that in the next section.
________________________________________________________________________________________________________________________________________________