通过Executors创建创建的线程池分析I

对于线程池主要关心的内容有:线程池的大小控制、池中的队列控制、池饱和时的策略,下面我们从这三个角度来分析一下Executors生成的不同池。

1、ThreadPoolExecutor构造函数介绍:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

 第一个corePoolSize:JDK文档里定义为核心线程数,很耐人寻味的一个词,但是很重要,我们在下面的分析中经常会使用它,对于它的理解,我们最后在取总结;

第二个maximumPoolSize:线程池中可容纳的最大线程个数;

第三个keepAliveTime:当当前的线程数大于核心线程数时, 多余的空闲线程等待新任务的最长时间,如果没有新任务进入则多余的空闲线程将被销毁;

第四个unit:第三个参数的单位;

第五个workQueue:保持任务的队列(任务队列基本有三种形式:无限队列、有限队列和同步移交);

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

相对第一个构造函数多了一个handler,这个参数用来指定线程池饱和时的策略(饱和策略主要有:中止、遗弃、遗弃最旧的、调用者运行);还有两个构造函数这里就不再列出了,不同点就是可以指定threadFactory。

 

2、newCachedThreadPool:

有了上面的知识,我们来分析一下newCachedThreadPool构造的线程池,先看一下方法内容:

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

这里指定了,核心线程数为0,线程池中最大的线程数为0x7fffffff,当前线程个数大于核心线程数时,多余线程的存活时间为60秒,任务队列指定的使用的是SynchronousQueue,饱和策略使用的是AbortPolicy(中止策略)

下面我们来看一下newCachedThreadPool创建的线程池:线程池大小、池中的队列、池饱和时的策略

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
    }

 上面的代码是线程池执行任务的核心代码,我们分析一下,

poolSize记录的是当前线程池中的线程个数,corePoolSize是我们指定的线程池的核心线程个数,在newCachedThreadPool创建的线程池中poolSize >= corePoolSize永远都会成立,因为corePoolSize被我们设置成0,当前线程池的大小永远是大于等于0的,接下来看一下内层代码,先判断一下当前线程池的状态是否在运行状态,然后调用workQueue.offer(command)方法,在newCachedThreadPool创建的线程池中workQueue是SynchronousQueue,因此调用的是SynchronousQueue的如下代码:

public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        return transferer.transfer(e, true, 0) != null;
}

这里的transferer是TransferStack,由于我们timed和nanos(transfer方法的第二和第三个参数)设置为true和0,因此会永远执行transfer方法的如下代码:

if (timed && nanos <= 0) {      // can't wait
      if (h != null && h.isCancelled())
             casHead(h, h.next);     // pop cancelled node
       else
            return null;
} 

而这里transferer.transfer(e, true, 0)方法的返回是null,因此workQueue.offer(command)方法永远是false。

这样机只会执行:

else if (!addIfUnderMaximumPoolSize(command))
           reject(command);

 这个分支,addIfUnderMaximumPoolSize方法如下:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
        Thread t = null;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (poolSize < maximumPoolSize && runState == RUNNING)
                t = addThread(firstTask);
        } finally {
            mainLock.unlock();
        }
        if (t == null)
            return false;
        t.start();
        return true;
}

 这里又会判断poolSize当前线程池中线程个数是否小于maximumPoolSize线程池中可容纳的最大个数,如果小于则创建一个线程来执行对应的任务返回true,如果不小于则会返回false,当返回false时,回去执行reject(command)方法,会根据中止的策略来处理新提交的任务。

这里要说明一下SynchronousQueue:

SynchronousQueue并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到SynchronousQueue中时必须有另一个线程正在等待接受移交的任务,如果没有这样的线程存在,只要当前池的大小还小于最大值,线程池就会创建一个新的线程来执行这个任务,因此使用SynchronousQueue作为线程池队列的前提是,要么池的大小是无限的或者可以接受被拒绝策略。

例子:

public class CachedThreadPoolTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		((ThreadPoolExecutor)service).setMaximumPoolSize(3);
		((ThreadPoolExecutor)service).setCorePoolSize(2);
		((ThreadPoolExecutor)service).setKeepAliveTime(20, TimeUnit.SECONDS);
		service.submit(new SleepTask());
		service.submit(new SleepTask());
		service.submit(new SleepTask());
		try{
			service.submit(new SleepTask());--将被拒绝
		}catch(RejectedExecutionException e){
			System.out.println("Rejected1");
		}
		
		try{
			service.submit(new SleepTask());--将被拒绝
		}catch(RejectedExecutionException e){
			System.out.println("Rejected2");
		}
	}

}

 结果:

Rejected1
Rejected2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3

SleepTask代码

public class SleepTask implements Callable<String> {

	@Override
	public String call() throws Exception {
		TimeUnit.SECONDS.sleep(10);
		System.out.println(Thread.currentThread().getName());
		return Thread.currentThread().getName();
	}

}
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值