为何要用线程池1?java
服务器应用程序中常常出现的状况是:单个任务处理的时间很短而请求的数目倒是巨大的。spring
构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就建立一个新线程,而后在新线程中为请求服务。实际上,对于原型开发这种方法工做得很好,但若是试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。数据库
每一个请求对应一个线程(thread-per-request)方法的不足之一是:为每一个请求建立一个新线程的开销很大;为每一个请求建立新线程的服务器在建立和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。除了建立和销毁线程的开销以外,活动的线程也消耗系统资源(线程的生命周期!)。在一个JVM 里建立太多的线程可能会致使系统因为过分消耗内存而用完内存或“切换过分”。为了防止资源不足,服务器应用程序须要一些办法来限制任何给定时刻处理的请求数目。编程
线程池为线程生命周期开销问题和资源不足问题提供了解决方案。经过对多个任务重用线程,线程建立的开销被分摊到了多个任务上。其好处是,由于在请求到达时线程已经存在,因此无心中也消除了线程建立所带来的延迟。这样,就能够当即为请求服务,使应用程序响应更快。并且,经过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到得到一个线程来处理为止,从而能够防止资源不足。服务器
为何要使用线程池2?多线程
操做系统建立线程、切换线程状态、终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情。 并发
大多数实际场景中是这样的:处理某一次请求的时间是很是短暂的,可是请求数量是巨大的。这种技术背景下,若是咱们为每个请求都单首创建一个线程,那么物理机的全部资源基本上都被操做系统建立线程、切换线程状态、销毁线程这些操做所占用,用于业务请求处理的资源反而减小了。因此最理想的处理方式是,将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理自己。 ide
另外,一些操做系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操做系统会变得不稳定。这也是咱们要限制线程数量的缘由。性能
----------------this
线程池的替代方案
线程池远不是服务器应用程序内使用多线程的惟一方法。如同上面所提到的,有时,为每一个新任务生成一个新线程是十分明智的。然而,若是任务建立过于频繁而任务的平均处理时间太短,那么为每一个任务生成一个新线程将会致使性能问题。
另外一个常见的线程模型是为某一类型的任务分配一个后台线程与任务队列。
每一个任务对应一个线程方法和单个后台线程(single-background-thread)方法在某些情形下都工做得很是理想。每一个任务一个线程方法在只有少许运行时间很长的任务时工做得十分好。而只要调度可预见性不是很重要,则单个后台线程方法就工做得十分好,如低优先级后台任务就是这种状况。然而,大多数服务器应用程序都是面向处理大量的短时间任务或子任务,所以每每但愿具备一种可以以低开销有效地处理这些任务的机制以及一些资源管理和定时可预见性的措施。线程池提供了这些优势。
----------------
工做队列
就线程池的实际实现方式而言,术语“线程池”有些令人误解,由于线程池“明显的”实如今大多数情形下并不必定产生咱们但愿的结果。术语“线程池”先于 Java 平台出现,所以它多是较少面向对象方法的产物。然而,该术语仍继续普遍应用着。
虽然咱们能够轻易地实现一个线程池类,其中客户机类等待一个可用线程、将任务传递给该线程以便执行、而后在任务完成时将线程归还给池,但这种方法却存在几个潜在的负面影响。例如在池为空时,会发生什么呢?试图向池线程传递任务的调用者都会发现池为空,在调用者等待一个可用的池线程时,它的线程将阻塞。咱们之因此要使用后台线程的缘由之一经常是为了防止正在提交的线程被阻塞。彻底堵住调用者,如在线程池的“明显的”实现的状况,能够杜绝咱们试图解决的问题的发生。
咱们一般想要的是同一组固定的工做线程相结合的工做队列,它使用 wait() 和 notify() 来通知等待线程新的工做已经到达了。该工做队列一般被实现成具备相关监视器对象的某种链表。清单 1 显示了简单的合用工做队列的示例。尽管 Thread API 没有对使用 Runnable 接口强加特殊要求,但使用 Runnable 对象队列的这种模式是调度程序和工做队列的公共约定。
----------------
使用线程池的风险
虽然线程池是构建多线程应用程序的强大机制,但使用它并非没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的全部并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足,并发错误,线程泄漏,请求过载。
----------------
有效使用线程池的准则
只要您遵循几条简单的准则,线程池能够成为构建服务器应用程序的极其有效的方法:
(1)不要对那些同步等待其它任务结果的任务排队。这可能会致使上面所描述的那种形式的死锁,在那种死锁中,全部线程都被一些任务所占用,这些任务依次等待排队任务的结果,而这些任务又没法执行,由于全部的线程都很忙。
(2)在为时间可能很长的操做使用合用的线程时要当心。若是程序必须等待诸如 I/O 完成这样的某个资源,那么请指定最长的等待时间,以及随后是失效仍是将任务从新排队以便稍后执行。这样作保证了:经过将某个线程释放给某个可能成功完成的任务,从而将最终取得 某些 进展。
理解任务。要有效地调整线程池大小,您须要理解正在排队的任务以及它们正在作什么。它们是 CPU 限制的(CPU-bound)吗?它们是 I/O 限制的(I/O-bound)吗?您的答案将影响您如何调整应用程序。若是您有不一样的任务类,这些类有着大相径庭的特征,那么为不一样任务类设置多个工做队 列可能会有意义,这样能够相应地调整每一个池。
----------------
调整池的大小
调整线程池的大小基本上就是避免两类错误:线程太少或线程太多。幸运的是,对于大多数应用程序来讲,太多和太少之间的余地至关宽。
线程池的最佳大小取决于可用处理器的数目以及工做队列中的任务的性质。若在一个具备 N 个处理器的系统上只有一个工做队列,其中所有是计算性质的任务,在线程池具备 N 或 N+1 个线程时通常会得到最大的 CPU 利用率。
处理器利用率不是调整线程池大小过程当中的惟一考虑事项。随着线程池的增加,您可能会碰到调度程序、可用内存方面的限制,或者其它系统资源方面的限制,例如套接字、打开的文件句柄或数据库链接等的数目。
----------------
无须编写您本身的池
Doug Lea 编写了一个优秀的并发实用程序开放源码库 util.concurrent ,它包括互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类集合类以及几个工做队列实现。该包中的 PooledExecutor 类是一种有效的、普遍使用的以工做队列为基础的线程池的正确实现。
参考:http://blog.csdn.net/ichsonx/article/details/6265071 java 线程池 较详细文摘
参考:http://www.ibm.com/developerworks/cn/java/l-threadPool/ 线程池的介绍及简单实现
Java语言为咱们提供了两种基础线程池的选择:ScheduledThreadPoolExecutor 和 ThreadPoolExecutor。它们都实现了ExecutorService接口(注意,ExecutorService接口自己和“线程池”并无直接关系,它的定义更接近“执行器”,而“使用线程管理的方式进行实现”只是其中的一种实现方式)。这篇文章中,咱们主要围绕ThreadPoolExecutor类进行讲解。
----------------
线程池的建立
咱们能够经过java.util.concurrent.ThreadPoolExecutor来建立一个线程池。
经常使用构造方法为:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler)
处理过程
当一个任务经过execute(Runnable)方法欲添加到线程池时:
处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,若是三者都满了,使用handler处理被拒绝的任务。
当线程池中的线程数量大于 corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池能够动态的调整池中的线程数。
unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性: NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
handler有四个选择:
举例:
import java.io.Serializable; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TestThreadPool2{ public static void main(String[] args){ // 构造一个线程池(池中最少有2个线程,最多4个线程,池中的线程容许空闲3秒,线程池所使用的缓冲队列ArrayBlockingQueue且容量为3,线程池对拒绝任务的处理策略为:抛弃旧的任务) ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new ThreadPoolExecutor.DiscardOldestPolicy()); for (int i = 1; i <= 10; i++){ try{ // 产生一个任务,并将其加入到线程池 String task = "task@ " + i; System.out.println("put " + task); threadPool.execute(new ThreadPoolTask(task)); // 便于观察,等待一段时间 Thread.sleep(2); } catch (Exception e){ e.printStackTrace(); } } } } /** * 线程池执行的任务 */ class ThreadPoolTask implements Runnable, Serializable{ private static final long serialVersionUID = 0; // 保存任务所须要的数据 private Object threadPoolTaskData; ThreadPoolTask(Object tasks){ this.threadPoolTaskData = tasks; } public void run(){ // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句 System.out.println(Thread.currentThread().getName()); System.out.println("start .." + threadPoolTaskData); try{ // //便于观察,等待一段时间 Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } threadPoolTaskData = null; } public Object getTask(){ return this.threadPoolTaskData; } }
说明:
这个小组里面队员至少有两个,若是他们两个忙不过来,任务就被放到任务列表里面。若是积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。可是基于成本的考虑,不能雇佣太多的队员,至多只能雇佣 4个。若是四个队员都在忙时,再有新的任务,这个小组就处理不了了,任务就会被经过一种策略来处理,咱们的处理方式是不停的派发,直到接受这个任务为止(更残忍!呵呵)。由于队员工做是须要成本的,若是工做很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,可是,为了小组的正常运转,即便工做再闲,小组的队员也不能少于两个。
四、经过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制,改变这两个值就能够观察不一样速率下程序的工做状况。
五、经过调整4中所指的数据,再加上调整任务丢弃策略,换上其余三种策略,就能够看出不一样策略下的不一样处理方式。
六、对于其余的使用方法,参看jdk的帮助,很容易理解和使用。
再举一个例子:
import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorTest { private static int queueDeep = 4; public void createThreadPool(){ /* * 建立线程池,最小线程数为2,最大线程数为4,线程池维护线程的空闲时间为3秒, * 使用队列深度为4的有界队列,若是执行程序还没有关闭,则位于工做队列头部的任务将被删除, * 而后重试执行程序(若是再次失败,则重复此过程),里面已经根据队列深度对任务加载进行了控制。 */ ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueDeep), new ThreadPoolExecutor.DiscardOldestPolicy()); // 向线程池中添加 10 个任务 for (int i = 0; i < 10; i++){ try{ Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } while (getQueueSize(tpe.getQueue()) >= queueDeep){ System.out.println("队列已满,等3秒再添加任务"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } TaskThreadPool ttp = new TaskThreadPool(i); System.out.println("put i:" + i); tpe.execute(ttp); } tpe.shutdown(); } private synchronized int getQueueSize(Queue queue) { return queue.size(); } public static void main(String[] args) { ThreadPoolExecutorTest test = new ThreadPoolExecutorTest(); test.createThreadPool(); } class TaskThreadPool implements Runnable { private int index; public TaskThreadPool(int index) { this.index = index; } public void run() { System.out.println(Thread.currentThread() + " index:" + index); try{ Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
spring线程池ThreadPoolExecutor配置而且获得任务执行的结果
Java并发包:ExecutorService和ThreadPoolExecutor
Java并发包:Java Fork and Join using ForkJoinPool
Java并发包:ScheduledExecutorService(一种安排任务执行的ExecutorService)
----------------
合理的配置线程池
要想合理的配置线程池,就必须首先分析任务特性,能够从如下几个角度来进行分析:
任务性质不一样的任务能够用不一样规模的线程池分开处理。
咱们能够经过Runtime.getRuntime().availableProcessors()方法得到当前设备的CPU个数。
优先级不一样的任务可使用优先级队列PriorityBlockingQueue来处理。
它可让优先级高的任务先获得执行,须要注意的是若是一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不一样的任务能够交给不一样规模的线程池来处理,或者也可使用优先级队列,让执行时间短的任务先执行。
依赖数据库链接池的任务,由于线程提交SQL后须要等待数据库返回结果,若是等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
建议使用有界队列,有界队列能增长系统的稳定性和预警能力,能够根据须要设大一点,好比几千。
有一次咱们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,经过排查发现是数据库出现了问题,致使执行SQL变得很是缓慢,由于后台任务线程池里的任务全是须要向数据库查询和插入数据的,因此致使线程池里的工做线程所有阻塞住,任务积压在线程池里。
若是当时咱们设置成无界队列,线程池的队列就会愈来愈多,有可能会撑满内存,致使整个系统不可用,而不仅是后台任务出现问题。
固然咱们的系统全部的任务是用的单独的服务器部署的,而咱们使用不一样规模的线程池跑不一样类型的任务,可是出现这样问题时也会影响到其余任务。