JAVA并发-ThreadPoolExecutor线程池的使用

在博客 JAVA并发-Executor任务执行框架中曾说过,Executors有4种工厂方法,即newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool。 这些工厂方法其实都是创建了一些不同执行策略的ThreadPoolExecutor。

我们可以创建自定义执行策略的ThreadPoolExecutor。ThreadPoolExecutor的通用构造函数如下:
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

上面的构造函数中有7个参数,依次为: 线程池基本数量,线程池最大数量,线程活跃时间,线程活跃时间单位,任务等待队列,线程工厂,饱和策略。

线程池基本数量:线程池中常驻线程的数量。
线程池最大数量:允许线程池创建的线程的最大数量。

在代码中通常不会固定线程池的大小,而是通过某种配置机制来设定,或者是通过Runtime.availableProcessors来动态的设定。一般来说,同一个线程池仅仅用来执行同一种类型的任务,这样可以更好的进行策略调整。

要正确的设置线程池的大小,需要考虑CPU数量(核心数),内存大小,任务类型(计算密集型或IO密集型),以及是否需要类似JDBC连接这样的稀有资源等问题。

通常计算密集型任务,线程池的大小设定为Ncpu+1(核心数+1)。IO密集型任务,线程池的大小设定为Ncpu*(W+C)/C(W为平均IO等待时间,C为平均计算时间)。在JAVA中,可以通过Runtime.getRuntime().availableProcessors()来获得CPU的数量(即总的核心数)。有些时候,线程池中执行的任务需要某些稀有资源,这个时候:计算每个任务对该资源的需求量,然后用资源的总量除以每个任务的需求量,所得结果就是可并发的任务总量(即最大线程数量)。

线程活跃时间:如果当前线程池中的线程数量大于基本数量,并且线程空闲时间大于活跃时间,那么这些线程将被销毁。

任务队列:如果任务的到达速度超过线程池的处理速度,那么等待被执行的任务会被放到等待队列中,这是一种缓存策略。但是 如果任务持续突增,那么任务队列可能会耗尽内存资源,此时考虑使用有界队列。ThreadPoolExecutor的基本任务队列方法有3种:无界队列,有界队列,同步队列。

饱和策略:如果任务队列被占满,就会对之后到来的任务使用饱和策略(一个任务被提交到已经关闭的Executor时,也会使用饱和策略)。 常用的饱和策略有AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy四种。AbortPolicy是默认的饱和策略,该策略抛出未检查的RejectedExecutionException。CallerRunnsPolicy使用调用者线程来执行本该被线程池执行的任务。DiscardPolicy会抛弃当前任务。DiscardOldestPolicy会抛弃等待队列中的队首任务,并将当前任务添加到队列中。饱和策略的设定方式如下:
ExecutorService es=new ThreadPoolExecutor(3, 5, 0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>(), 
				new ThreadPoolExecutor.CallerRunsPolicy());


工厂方法:每当线程池需要一个新的线程时,都是通过线程工厂方法来完成的。用户可以自定义自己的工厂方法,实现ThreadFactory接口就可以。
public interface ThreadFactory {
    Thread newThread(Runnable r);
}


ThreadPoolExecutor还提供了一些上下文的方法:比如beforeExecute(),afterExecute(),terminated()方法。其中:beforeExecute()在execute()之前被调用,afterExecute()在execute()之后被调用,terminated()是在线程池终止的时候被调用。下面是一个例子:

class MyThreadPoolExecutor extends ThreadPoolExecutor{
	public MyThreadPoolExecutor(int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            RejectedExecutionHandler handler) {
		// TODO Auto-generated constructor stub
		super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,handler);
	}
	
	private static ThreadLocal<Long> startTime=new ThreadLocal<Long>();
	private AtomicLong taskNums=new AtomicLong();
	private AtomicLong taskTimes=new AtomicLong();
	
	@Override
	protected void beforeExecute(Thread t, Runnable r) {
		super.beforeExecute(t, r);
		startTime.set(System.nanoTime());
	}
	@Override
	protected void afterExecute(Runnable r, Throwable t) {
		super.afterExecute(r, t);
		long time=System.nanoTime()-startTime.get();
		taskNums.incrementAndGet();
		taskTimes.addAndGet(time);
	}
	@Override
	protected void terminated() {
		super.terminated();
		System.out.println(taskTimes.get());
		System.out.println(taskNums.get());
		System.out.println(taskTimes.get()/taskNums.get());
	}
}

上面的线程池MyThreadPoolExecutor添加了统计任务数,总任务时间的功能。 其中每个任务的开始时间和结束时间是在同一个线程的beforeExecute()和afterExecute()方法中获得的,因此使用ThreadLocal类型的变量来记录开始时间;而任务数和任务总时间是共享变量,需要保证线程安全性,因此使用原子变量。

还可以使用信号量来控制任务的提交速度:
class MyExecutor {
	private Executor executor;
	private Semaphore sem;
	public MyExecutor(Executor exe,int taskNums){
		this.executor=exe;
		this.sem=new Semaphore(taskNums);
	}
	public void submit(final Runnable task) throws InterruptedException{
		try{
			sem.acquire();
			executor.execute(new Runnable() {
				@Override
				public void run() {
					try{
						task.run();						
					}finally{
						sem.release();
					}
				}
			});
			//由于默认使用AbortPolicy策略,所以可能抛出RejectedExecutionExecution
		}catch(RejectedExecutionException e){
			sem.release();
		}
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值