一、 演示

  1. public class ThreadPoolTest {
  2. static class MyThread implements Runnable {
  3. private String name;
  4.  
  5. public MyThread(String name) {
  6. this.name = name;
  7. }
  8.  
  9. @Override
  10. public void run() {
  11.  
  12. try {
  13. Thread.sleep(1000);
  14.  
  15. System.out.println(name + " finished job!") ;
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21.  
  22. public static void main(String[] args) {
  23. // 创建线程池,为了更好的明白运行流程,增加了一些额外的代码
  24. // BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);
  25. BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
  26. // BlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>();
  27. // BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
  28.  
  29. // AbortPolicy/CallerRunsPolicy/DiscardOldestPolicy/DiscardPolicy
  30. ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 5, TimeUnit.SECONDS,
  31. queue, new ThreadPoolExecutor.AbortPolicy());
  32.  
  33. for (int i = 0; i < 10; i++) {
  34. System.out.println("当前线程池大小[" + threadPool.getPoolSize() + "],当前队列大小[" + queue.size() + "]");
  35.  
  36. threadPool.execute(new MyThread("Thread" + i));
  37. }
  38. // 关闭线程池
  39. threadPool.shutdown();
  40. }
  41. }

There are three general strategies for queuing:

  1. Direct handoffs. A good default choice for a work queue is a SynchronousQueue that hands off tasks to threads without otherwise holding them. Here, an attempt to queue a task will fail if no threads are immediately available to run it, so a new thread will be constructed. This policy avoids lockups when handling sets of requests that might have internal dependencies. Direct handoffs generally require unbounded maximumPoolSizes to avoid rejection of new submitted tasks. This in turn admits the possibility of unbounded thread growth when commands continue to arrive on average faster than they can be processed.
  2. Unbounded queues. Using an unbounded queue (for example a LinkedBlockingQueue without a predefined capacity) will cause new tasks to wait in the queue when all corePoolSize threads are busy. Thus, no more than corePoolSize threads will ever be created. (And the value of the maximumPoolSize therefore doesn't have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each others execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.
  3. Bounded queues. A bounded queue (for example, an ArrayBlockingQueue) helps prevent resource exhaustion when used with finite maximumPoolSizes, but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to artificially low throughput. If tasks frequently block (for example if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.

排队有三种通用策略:

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁(A线程需要先运行,然后是B线程,SynchronousQueue可以保证这个执行顺序)。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

  2. 无界队列。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如, I/O密集型 ),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

二、构造函数及其参数含义

1. 当线程数 < 核心线程数时,创建线程

2. 线程数 >= 核心线程数,

2.1 任务队列未满时,将任务放入任务队列。

   2.2 任务队列已满

    2.2.1 线程数 < 最大线程数,创建线程

    2.2.2 线程数 > 最大线程数,抛出异常,拒绝任务

  1. public ThreadPoolExecutor(int corePoolSize, //核心线程的数量
  2. int maximumPoolSize, //最大线程数量
  3. long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
  4. TimeUnit unit, //存活时间的单位
  5. BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
  6. ThreadFactory threadFactory, //创建新线程使用的工厂
  7. RejectedExecutionHandler handler // 当任务无法执行时的处理器
  8. ) {...}

执行判断

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4.  
  5. int c = ctl.get();
  6. //1.当前池中线程比核心数少,新建一个线程执行任务
  7. if (workerCountOf(c) < corePoolSize) {
  8. if (addWorker(command, true))
  9. return;
  10. c = ctl.get();
  11. }
  12. //2.核心池已满,但任务队列未满,添加到队列中,所以:如果是无界队列,添加的任务超过核心线程后,不会创建非核心线程,maximumPoolSize参数无效
  13. if (isRunning(c) && workQueue.offer(command)) {
  14. int recheck = ctl.get();
  15. if (! isRunning(recheck) && remove(command)) //如果这时被关闭了,拒绝任务
  16. reject(command);
  17. else if (workerCountOf(recheck) == 0) //如果之前的线程已被销毁完,新建一个线程
  18. addWorker(null, false);
  19. }
  20. //3.核心池已满,队列已满,试着创建一个新线程
  21. else if (!addWorker(command, false))
  22. reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
  23. }

不同类型线程池构造

  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. }
  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }
  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }

三、如何确定系统的处理能力、线程数

1. 处理能力

利特尔法则(Little's law),在一个稳定的系统中,长时间观察到的平均顾客数量L = 长时间观察到的有效到达速率λ * 平均每个顾客在系统中花费的时间W,即L = λW。

假定我们所开发的并发服务器,并发的访问速率是:1000客户/分钟,每个客户在该服务器上将花费平均0.5分钟,根据little's law规则,

在任何时刻,服务器将承担1000×0.5=500个客户量的业务处理。假定过了一段时间,由于客户群的增大,并发的访问速率提升为2000客户/分钟。

在这样的情况下,我们该如何改进我们系统的性能?

根据little's law规则,有两种方案:

第一:提高服务器并发处理的业务量,即提高到2000×0.5=1000。 或者

第二:减少服务器平均处理客户请求的时间,即减少到:500 / 2000 = 0.25。

2. 线程数

  • 如果是CPU密集型应用,则线程池大小设置为N+1

  • 如果是IO密集型应用,则线程池大小设置为2N+1

是否使用线程池就一定比使用单线程高效呢?

答案是否定的,比如Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/s。从线程这个角度来看,部分原因在于:

  • 多线程带来线程上下文切换开销,单线程就没有这种开销

更本质的原因在于:Redis基本都是内存操作,这种情况下单线程可以很高效地利用CPU。而多线程适用场景一般是:存在相当比例的IO和网络操作。

所以即使有上面的简单估算方法,也许看似合理,但实际上也未必合理,都需要结合系统真实情况(比如是IO密集型或者是CPU密集型或者是纯内存操作)

和硬件环境(CPU、内存、硬盘读写速度、网络状况等)来不断尝试达到一个符合实际的合理估算值。

四、Worker

  1. /**
  2. * Class Worker mainly maintains interrupt control state for
  3. * threads running tasks, along with other minor bookkeeping.
  4. * This class opportunistically extends AbstractQueuedSynchronizer
  5. * to simplify acquiring and releasing a lock surrounding each
  6. * task execution. This protects against interrupts that are
  7. * intended to wake up a worker thread waiting for a task from
  8. * instead interrupting a task being run. We implement a simple
  9. * non-reentrant mutual exclusion lock rather than use
  10. * ReentrantLock because we do not want worker tasks to be able to
  11. * reacquire the lock when they invoke pool control methods like
  12. * setCorePoolSize. Additionally, to suppress interrupts until
  13. * the thread actually starts running tasks, we initialize lock
  14. * state to a negative value, and clear it upon start (in
  15. * runWorker).
  16. */
  17. private final class Worker
  18. extends AbstractQueuedSynchronizer
  19. implements Runnable

Worker是ThreadPoolExecutor的静态内部类,主要是对Thread对象的包装,一个Worker内部有一个Thread对象。

Worker继承自AQS来实现一个简单互斥锁,每一个任务的执行前和执行后都会分别获取和释放一次锁, 这样做是为了让线程执行任务时屏蔽中断操作。

那么为什么不用ReentrantLock呢?其实是为了避免在任务执行中修改线程池的变量和状态,不能用可重入锁。

参考:

Java线程池机制

ThreadPoolExecutor 线程池任务队列分析 与 利特尔法则(Little's law)的更多相关文章

  1. [转]ThreadPoolExecutor线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第 ...

  2. ThreadPoolExecutor线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第 ...

  3. ElasticSearch 线程池类型分析之 ResizableBlockingQueue

    ElasticSearch 线程池类型分析之 ResizableBlockingQueue 在上一篇文章 ElasticSearch 线程池类型分析之 ExecutorScalingQueue的末尾, ...

  4. JAVA线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.第三:提 ...

  5. Java 线程池原理分析

    1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...

  6. 聊聊并发(三)Java线程池的分析和使用

    1.    引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行. ...

  7. ElasticSearch 线程池类型分析之SizeBlockingQueue

    ElasticSearch 线程池类型分析之SizeBlockingQueue 尽管前面写好几篇ES线程池分析的文章(见文末参考链接),但都不太满意.但从ES的线程池中了解到了不少JAVA线程池的使用 ...

  8. ElasticSearch 线程池类型分析之 ExecutorScalingQueue

    ElasticSearch 线程池类型分析之 ExecutorScalingQueue 在ElasticSearch 线程池类型分析之SizeBlockingQueue这篇文章中分析了ES的fixed ...

  9. 源码剖析ThreadPoolExecutor线程池及阻塞队列

    本文章对ThreadPoolExecutor线程池的底层源码进行分析,线程池如何起到了线程复用.又是如何进行维护我们的线程任务的呢?我们直接进入正题: 首先我们看一下ThreadPoolExecuto ...

随机推荐

  1. 美团HD(6)-添加搜索遮罩

    DJSelectCityViewController.m /** SearchBar开始编辑 */ - (void)searchBarTextDidBeginEditing:(UISearchBar ...

  2. unity mac 破解教程

    1.安装好软件,我们解压破解包,有下面两个文件,Unity 和Unity_v5.x.ulf,放桌面.     2.打开终端.       3.输入文件地址,注意,我这上边的用户名,记得改成自己的用户名 ...

  3. iOS一些基础面试题

    Part One 别人问你你都感觉这尼玛说啥的基础面试题 1.UIWindow和UIView和 CALayer 的联系和区别? 答:UIView是视图的基类,UIViewController是视图控制 ...

  4. javaweb+mysql+c3p0ajax实现三级联动

    1.首先要导入jar文件: c3p0-0.9.5.1.jarcommons-beanutils-1.7.0.jarcommons-collections-3.2.jarcommons-dbutils- ...

  5. 矩阵中的路径 剑指offer65题

    include "stdafx.h" #include<vector> #include<algorithm> #include<string> ...

  6. void bind(String sName,Object object);――绑定:把名称同对象关联的过程

    void bind(String sName,Object object);――绑定:把名称同对象关联的过程 void rebind(String sName,Object object);――重新绑 ...

  7. SlidingMenu官方实例分析1——ExampleListActivity

    1.SlidingMenuDemo下载: 由AndroidManifest.xml能看出项目是从ExampleListActivity启动的: ExampleListActivity继承了Sherlo ...

  8. Rightscale & Amazon

    原先一直以为Rightscale是Amazno旗下的一个产品,今天才知道是Amazon partner - -||,实在汗颜. Rightscale也是一个很强大的公司,提供跨云解决方案...(呃,原 ...

  9. 12个十分实用的JavaScript小技巧

    12个非常实用的JavaScript小技巧 在这篇文章中将给大家分享12个有关于JavaScript的小技巧.这些小技巧可能在你的实际工作中或许能帮助你解决一些问题. 使用!!操作符转换布尔值 有时候 ...

  10. Android初体验-D3

    1. UI界面布局. (即可用XML控制布局也可采用Java代码布局,不过在实际应用中是两者混合控制UI界面,为什么呢,因为XML适用于固定的不易改变的组件布局,Java程序控制常变的组件...其控制 ...