线程池基础知识及配置使用

线程池的理解http://www.importnew.com/17633.html

线程池Executors类、ThreadPoolExecutor构造、BlockingQueue种类

ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别 http://dongxuan.iteye.com/blog/901689#comments

先了解一下BlockingQueue

所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将由rejectedExecutionHandler任务拒绝处理器处理。

 

一共有三种类型的queue

排队有三种通用策略:

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

ArrayBlockingQueue是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。如果任务很多,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

 

ThreadPoolExecutor完整的构造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

不同的线程池就是使用不同参数构造出来的ThreadPoolExecutor

 

常见的四种线程池和区别https://blog.csdn.net/qq_34952110/article/details/78086085

Executors类中包含多个生成ThreadPoolExecutor的方法

常用的几个为

固定大小线程池:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

这是一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快。正规的并发线程,多用于服务器。固定的线程数由系统资源设置。

适用:执行长期的任务,性能好很多

单个后台线程池:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

只有一个核心线程,通过指定的顺序将任务一个个丢到线程,都乖乖的排队等待执行,不处理并发的操作,不会被回收。确定就是一个人干活效率慢。

适用:一个任务一个任务执行的场景

不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,如果任务内存大一些,会耗尽系统资源。

 

无界线程池:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

只有非核心线程,最大线程数很大(Int.Max(values)),它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收。缺点就是没有考虑到系统的实际内存大小。

适用:执行很多短期异步的小程序或者负载较轻的服务器

SynchronousQueue,该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。比如,我先添加一个元素,接下来如果继续想尝试添加则会阻塞,直到另一个线程取走一个元素,反之亦然。(想到什么?就是缓冲区为1的生产者消费者模式^_^)

首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加,在这里不是核心线程便是新创建的线程。

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。

在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中
 

定时线程池:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

这个线程池就厉害了,是唯一一个有延迟执行和周期重复执行的线程池。它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收。

适用:周期性执行任务的场景

 

总结:

  1. ThreadPoolExecutor的使用还是很有技巧的。
  2. 使用无界queue可能会耗尽系统资源。
  3. 使用有界queue可能不能很好的满足性能,需要调节线程数和queue大小
  4. 线程数自然也有开销,所以需要根据不同应用进行调节。

 

线程池ThreadPoolExecutor参数设置

http://blog.csdn.net/zhouhl_cn/article/details/7392607

https://www.cnblogs.com/waytobestcoder/p/5323130.html

ThreadPoolExecutor类可设置的参数主要有:

  • corePoolSize 核心线程数

核心线程会一直存活,即使没有任务需要处理。

当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。

核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

 

  • maxPoolSize 最大线程数
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。
如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

 

  • keepAliveTime 线程空闲时间

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。

如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

 

  • allowCoreThreadTimeout 允许核心线程超时

是否允许核心线程空闲退出,默认值为false。

  • queueCapacity 任务队列容量(阻塞队列)

任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

 

  • rejectedExecutionHandler:任务拒绝处理器
  • 两种情况会拒绝处理任务:
    • 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
    • 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
  • 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
  • ThreadPoolExecutor类有几个内部实现类来处理这类情况:
    • AbortPolicy 丢弃任务,抛运行时异常
    • CallerRunsPolicy 执行任务
    • DiscardPolicy 忽视,什么都不会发生
    • DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
  • 实现RejectedExecutionHandler接口,可自定义处理器

 

线程池按以下行为执行任务

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    1. 若线程数小于最大线程数,创建线程
    2. 若线程数等于最大线程数,抛出异常,拒绝任务

系统负载

参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:

  • tasks,每秒需要处理的最大任务数量
  • tasktime,处理第个任务所需要的时间
  • responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。

 

参数设置

  • 默认值
    • corePoolSize=1
    • queueCapacity=Integer.MAX_VALUE
    • maxPoolSize=Integer.MAX_VALUE
    • keepAliveTime=60s
    • allowCoreThreadTimeout=false
    • rejectedExecutionHandler=AbortPolicy()

corePoolSize: 每秒需要多少个线程处理? 

每个任务需要tasktime秒处理,则每个线程每秒可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.1至1000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下每秒任务数小于200,最多时为1000,则corePoolSize可设置为20。

queueCapacity:(coreSizePool/tasktime)*responsetime

任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。

队列长度设置过大,会导致任务响应时间过长,切忌以下写法:

LinkedBlockingQueue queue = new LinkedBlockingQueue();

这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

maxPoolSize:(max(tasks)- queueCapacity)/(1/tasktime)

当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。

keepAliveTime:

线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。

allowCoreThreadTimeout:

默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

 

如何合理设置线程池大小

https://blog.csdn.net/u011519624/article/details/69263460

https://www.cnblogs.com/lengender-12/p/6869554.html

http://ifeve.com/how-to-calculate-threadpool-size/

 

要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:

  1. 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
  2. 任务的优先级:高、中、低。
  3. 任务的执行时间:长、中、短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接等。

性质不同的任务可以交给不同规模的线程池执行。

对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。

若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。

在这篇如何合理地估算线程池大小?文章中发现了一个估算合理值的公式

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
  • 1
  • 2

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
  • 1
  • 2

可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。

并发编程网上的一个问题
高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
(2)并发不高、任务执行时间长的业务要区分开看:
  a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务
  b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

 

在《linux多线程服务器端编程》中有一个思路,CPU计算和IO的阻抗匹配原则

如果线程池中的线程在执行任务时,密集计算所占的时间比重为P(0

这个经验公式的原理很简单,T个线程,每个线程占用P的CPU时间,如果刚好占满C个CPU,那么必有 T * P = C。

下面验证一下边界条件的正确性:

假设C = 8,P = 1.0,线程池的任务完全是密集计算,那么T = 8。只要8个活动线程就能让8个CPU饱和,再多也没用了,因为CPU资源已经耗光了。

假设C = 8,P = 0.5,线程池的任务有一半是计算,有一半在等IO上,那么T = 16.考虑操作系统能灵活,合理调度sleeping/writing/running线程,那么大概16个“50%繁忙的线程”能让8个CPU忙个不停。启动更多的线程并不能提高吞吐量,反而因为增加上下文切换的开销而降低性能。

如果P < 0.2,这个公式就不适用了,T可以取一个固定值,比如 5*C。另外公式里的C不一定是CPU总数,可以是“分配给这项任务的CPU数目”,比如在8核机器上分出4个核来做一项任务,那么C=4

 

最后来一个“Dark Magic”估算方法(因为我暂时还没有搞懂它的原理),使用下面的类:

package pool_size_calculate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;

/**
 * A class that calculates the optimal thread pool boundaries. It takes the
 * desired target utilization and the desired work queue memory consumption as
 * input and retuns thread count and work queue capacity.
 *
 * @author Niklas Schlimm
 *
 */
public abstract class PoolSizeCalculator {

	/**
	 * The sample queue size to calculate the size of a single {@link Runnable}
	 * element.
	 */
	private final int SAMPLE_QUEUE_SIZE = 1000;

	/**
	 * Accuracy of test run. It must finish within 20ms of the testTime
	 * otherwise we retry the test. This could be configurable.
	 */
	private final int EPSYLON = 20;

	/**
	 * Control variable for the CPU time investigation.
	 */
	private volatile boolean expired;

	/**
	 * Time (millis) of the test run in the CPU time calculation.
	 */
	private final long testtime = 3000;

	/**
	 * Calculates the boundaries of a thread pool for a given {@link Runnable}.
	 *
	 * @param targetUtilization
	 *            the desired utilization of the CPUs (0 <= targetUtilization <= 	 *            1) 	 * @param targetQueueSizeBytes 	 *            the desired maximum work queue size of the thread pool (bytes) 	 */ 	protected void calculateBoundaries(BigDecimal targetUtilization, 			BigDecimal targetQueueSizeBytes) { 		calculateOptimalCapacity(targetQueueSizeBytes); 		Runnable task = creatTask(); 		start(task); 		start(task); // warm up phase 		long cputime = getCurrentThreadCPUTime(); 		start(task); // test intervall 		cputime = getCurrentThreadCPUTime() - cputime; 		long waittime = (testtime * 1000000) - cputime; 		calculateOptimalThreadCount(cputime, waittime, targetUtilization); 	} 	private void calculateOptimalCapacity(BigDecimal targetQueueSizeBytes) { 		long mem = calculateMemoryUsage(); 		BigDecimal queueCapacity = targetQueueSizeBytes.divide(new BigDecimal( 				mem), RoundingMode.HALF_UP); 		System.out.println("Target queue memory usage (bytes): " 				+ targetQueueSizeBytes); 		System.out.println("createTask() produced " 				+ creatTask().getClass().getName() + " which took " + mem 				+ " bytes in a queue"); 		System.out.println("Formula: " + targetQueueSizeBytes + " / " + mem); 		System.out.println("* Recommended queue capacity (bytes): " 				+ queueCapacity); 	} 	/** 	 * Brian Goetz' optimal thread count formula, see 'Java Concurrency in 	 * Practice' (chapter 8.2) 	 *  	 * @param cpu 	 *            cpu time consumed by considered task 	 * @param wait 	 *            wait time of considered task 	 * @param targetUtilization 	 *            target utilization of the system 	 */ 	private void calculateOptimalThreadCount(long cpu, long wait, 			BigDecimal targetUtilization) { 		BigDecimal waitTime = new BigDecimal(wait); 		BigDecimal computeTime = new BigDecimal(cpu); 		BigDecimal numberOfCPU = new BigDecimal(Runtime.getRuntime() 				.availableProcessors()); 		BigDecimal optimalthreadcount = numberOfCPU.multiply(targetUtilization) 				.multiply( 						new BigDecimal(1).add(waitTime.divide(computeTime, 								RoundingMode.HALF_UP))); 		System.out.println("Number of CPU: " + numberOfCPU); 		System.out.println("Target utilization: " + targetUtilization); 		System.out.println("Elapsed time (nanos): " + (testtime * 1000000)); 		System.out.println("Compute time (nanos): " + cpu); 		System.out.println("Wait time (nanos): " + wait); 		System.out.println("Formula: " + numberOfCPU + " * " 				+ targetUtilization + " * (1 + " + waitTime + " / " 				+ computeTime + ")"); 		System.out.println("* Optimal thread count: " + optimalthreadcount); 	} 	/** 	 * Runs the {@link Runnable} over a period defined in {@link #testtime}. 	 * Based on Heinz Kabbutz' ideas 	 * (http://www.javaspecialists.eu/archive/Issue124.html). 	 *  	 * @param task 	 *            the runnable under investigation 	 */ 	public void start(Runnable task) { 		long start = 0; 		int runs = 0; 		do { 			if (++runs > 5) {
				throw new IllegalStateException("Test not accurate");
			}
			expired = false;
			start = System.currentTimeMillis();
			Timer timer = new Timer();
			timer.schedule(new TimerTask() {
				public void run() {
					expired = true;
				}
			}, testtime);
			while (!expired) {
				task.run();
			}
			start = System.currentTimeMillis() - start;
			timer.cancel();
		} while (Math.abs(start - testtime) > EPSYLON);
		collectGarbage(3);
	}

	private void collectGarbage(int times) {
		for (int i = 0; i < times; i++) {
			System.gc();
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				break;
			}
		}
	}

	/**
	 * Calculates the memory usage of a single element in a work queue. Based on
	 * Heinz Kabbutz' ideas
	 * (http://www.javaspecialists.eu/archive/Issue029.html).
	 *
	 * @return memory usage of a single {@link Runnable} element in the thread
	 *         pools work queue
	 */
	public long calculateMemoryUsage() {
		BlockingQueue queue = createWorkQueue();
		for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
			queue.add(creatTask());
		}
		long mem0 = Runtime.getRuntime().totalMemory()
				- Runtime.getRuntime().freeMemory();
		long mem1 = Runtime.getRuntime().totalMemory()
				- Runtime.getRuntime().freeMemory();
		queue = null;
		collectGarbage(15);
		mem0 = Runtime.getRuntime().totalMemory()
				- Runtime.getRuntime().freeMemory();
		queue = createWorkQueue();
		for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
			queue.add(creatTask());
		}
		collectGarbage(15);
		mem1 = Runtime.getRuntime().totalMemory()
				- Runtime.getRuntime().freeMemory();
		return (mem1 - mem0) / SAMPLE_QUEUE_SIZE;
	}

	/**
	 * Create your runnable task here.
	 *
	 * @return an instance of your runnable task under investigation
	 */
	protected abstract Runnable creatTask();

	/**
	 * Return an instance of the queue used in the thread pool.
	 *
	 * @return queue instance
	 */
	protected abstract BlockingQueue createWorkQueue();

	/**
	 * Calculate current cpu time. Various frameworks may be used here,
	 * depending on the operating system in use. (e.g.
	 * http://www.hyperic.com/products/sigar). The more accurate the CPU time
	 * measurement, the more accurate the results for thread count boundaries.
	 *
	 * @return current cpu time of current thread
	 */
	protected abstract long getCurrentThreadCPUTime();

}

然后自己继承这个抽象类并实现它的三个抽象方法,比如下面是我写的一个示例(任务是请求网络数据),其中我指定期望CPU利用率为1.0(即100%),任务队列总大小不超过100,000字节:

package pool_size_calculate;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class SimplePoolSizeCaculatorImpl extends PoolSizeCalculator {

	@Override
	protected Runnable creatTask() {
		return new AsyncIOTask();
	}

	@Override
	protected BlockingQueue createWorkQueue() {
		return new LinkedBlockingQueue(1000);
	}

	@Override
	protected long getCurrentThreadCPUTime() {
		return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
	}

	public static void main(String[] args) {
		PoolSizeCalculator poolSizeCalculator = new SimplePoolSizeCaculatorImpl();
		poolSizeCalculator.calculateBoundaries(new BigDecimal(1.0), new BigDecimal(100000));
	}

}

/**
 * 自定义的异步IO任务
 * @author Will
 *
 */
class AsyncIOTask implements Runnable {

	@Override
	public void run() {
		HttpURLConnection connection = null;
		BufferedReader reader = null;
		try {
			String getURL = "http://baidu.com";
			URL getUrl = new URL(getURL);

			connection = (HttpURLConnection) getUrl.openConnection();
			connection.connect();
			reader = new BufferedReader(new InputStreamReader(
					connection.getInputStream()));

			String line;
			while ((line = reader.readLine()) != null) {
				// empty loop
			}
		}

		catch (IOException e) {

		} finally {
			if(reader != null) {
				try {
					reader.close();
				}
				catch(Exception e) {

				}
			}
			connection.disconnect();
		}

	}

}

得到的输出如下:

Target queue memory usage (bytes): 100000
createTask() produced pool_size_calculate.AsyncIOTask which took 40 bytes in a queue
Formula: 100000 / 40
* Recommended queue capacity (bytes): 2500
Number of CPU: 4
Target utilization: 1
Elapsed time (nanos): 3000000000
Compute time (nanos): 47181000
Wait time (nanos): 2952819000
Formula: 4 * 1 * (1 + 2952819000 / 47181000)
* Optimal thread count: 256

推荐的任务队列大小为2500,线程数为256,有点出乎意料之外。我可以如下构造一个线程池:

ThreadPoolExecutor pool =
 new ThreadPoolExecutor(256, 256, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(2500));

关于ThreadPoolExecutor 调用RejectedExecutionHandler的机制

https://my.oschina.net/u/169390/blog/97415

任务被拒绝后,可通过实现RejectedExecutionHandler,来对被拒绝任务进行后续操作。

 

线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
  • largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数。

通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:

1 protected void beforeExecute(Thread t, Runnable r) { }

你可能感兴趣的:(线程池基础知识及配置使用)