JAVA线程池
基本用法
先来看个基本用法
ExecutorServiceexecutor=Executors.newFixedThreadPool(3);
executor.execute(newRunnable() {
@Override
public void run() {
//do something
}
});
上边代码创建了一个线程池,里面是有固定3个线程的线程池,然后使用execute执行一个任务,把这个runnable丢到线程池里执行。
概述
ExecutorService是线程池的管理类
Executor是线程池创建的工厂类
创建线程池一般会调用Executor的newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor的这四个方法之一,来创建需要的线程池。
线程池,一般有几个基本参数
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime超时时间
线程池的类是ThreadPoolExecutor,corePoolSize的作用主要是控制线程的停止。
只要线程数大于corePoolSize,就会触发以下逻辑。如果某个线程执行完他的任务了,会尝试去任务队列中取,如果在keepAliveTime内没取到任务(可能任务队列为空),那就会停止此线程,并且提醒其他idle的线程也停止,这样总线程数就降下来了。
而线程数降到corePoolSize之后就不会去停止线程了。这样线程池中一直会有corePoolSize个线程,所以有人称为核心线程,当然要记住核心线程不是一种特殊的线程,说不定这个时候线程池里的核心线程是这些,过一会线程池里的核心线程是另一些了,核心线程只是线程池的一种概念,并不是指某个线程的属性。
还有一种特殊情况,如果把allowCoreThreadTimeOut设置为true,那corePoolSize就无法控制线程的停止了,任何线程只要空闲时间超过keepAliveTime就会停止
线程池执行任务规则如下:
1、 初始状态线程池内没有线程,来了任务,就会立刻创建线程。
2、 当前线程数小于corePoolSize,那就来一个任务,创建一个线程。
3、 当前线程数达到corePoolSize,来了个任务,那就丢到任务队列里面去排队,等待某个线程执行完他当前的任务再来任务队列中取
4、 如果任务队列也满了,但是当前总线程数不超过maximumPoolSize,那就启动一个额外线程来处理。此时的线程数大于corePoolSize,小于maximumPoolSize,此时会触发线程闲置久了就停止的逻辑。
5、 如果任务队列也满了,但是当前总线程数到达maximumPoolSize,那就拒绝执行任务,调用一个RejectedExecutionHandler的rejectedExecution来通知调用者。
相关代码可以查看java.util.concurrent.ThreadPoolExecutor#execute
我们再来看看刚才四种方法产生的不同的线程池。
newFixedThreadPool
newFixedThreadPool得到的是ThreadPoolExecutor,里面只有核心线程,没有非核心线程,所有线程都处于活动状态,核心线程的数量固定,任务队列大小没有限制。所以,只用考虑上边的规则1和规则2。来了任务之后,核心线程有空就交给核心线程处理,核心线程没空就在任务队列里等待。所有线程一直开启,可以很快的响应任务,但是闲置状态比较浪费资源。适合处理少量耗时大的任务。
代码如下
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
newCachedThreadPool
newCachedThreadPool得到的也是ThreadPoolExecutor,与newFixedThreadPool对比很明显,newCachedThreadPool得到的线程池,没有核心线程,只有非核心线程,线程的最大数量是Integer.MAX_VALUE,所有线程的闲置时间不能超过60s,否则会被回收。这里的任务队列是SynchronousQueue,可以看做空,无法存储任务,那么一个任务到来之后,就检查是否有闲置的线程,有就交给闲置的线程去处理,如果没有就新开启一个线程去处理。闲置状态所有线程都会因为超时被停止,占资源少。有了任务得去开启线程,响应没有FixedThreadPool来的快,适合频繁的低耗时任务。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
newScheduledThreadPool
newScheduledThreadPool得到的是ScheduledThreadPoolExecutor,他是ThreadPoolExecutor的子类。这个线程池里有一定数量的核心线程,也有非核心线程,线程总数量为Integer.MAX_VALUE。非核心线程的保活时间为10ms,一般用完就很快被回收。
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
这个线程池的主要用法如下,主要是用于带控制的任务,比如多久时间以后执行某个任务,按什么样的周期执行任务,主要用于定时执行任务。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
典型用法:
//在2000ms之后执行某个任务
pool.schedule(runnable,2000,TimeUnit.MILLISECONDS)
//10ms之后执行,2000ms执行依次
pool.scheduleAtFixedRate(runnable,10,2000,TimeUnit.MILLISECONDS)
周期性的执行任务的时候,如果其中某次出了异常,那就会中断,不在继续执行任务。所有最好把任务加上异常捕获。
比如
private static class BusinessTask implements Runnable{
@Override
public void run() {
//捕获所有的异常,保证定时任务能够继续执行
try{
System.out.println("任务开始...");
//doBusiness();
System.out.println("任务结束...");
}catch (Throwable e) {
// donothing
}
}
}
newSingleThreadExecutor
再来看newSingleThreadExecutor,这个得到的线程池和前面几个都不同,得到FinalizableDelegatedExecutorService,其他几个都是ThreadPoolExecutor或者他的子类。这个类顾名思义就是,本质上只有一个线程。所有任务都会依次在这个任务中执行,可以有效保证顺序。比如打日志到sd卡时,就必须保证有序。
如何配置线程池大小:
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
参考文献
android开发艺术探索
https://segmentfault.com/a/1190000000371905
http://blog.csdn.net/nei504293736/article/details/7534084
http://www.cnblogs.com/dolphin0520/p/3932921.html