线程池初探
日常开发中,我们经常会有一些任务需要在额外的线程中执行。尤其是在Android中,我们不能阻塞主线程,我们更加需要异步地去完成某些任务。最原始的方式便是new一个Thread,然后通过回调的方式处理结果。
当然这种方式并不好,因为线程的创建/销毁,以及切换上下文都会带来系统开销,频繁地重复这个过程很大程度上会影响处理效率。那如果能复用之前创建的线程,是不是就能避免线程创建/销毁带来的开销了呢?
我们聪明的前辈们考虑到了这一点,给我们带来了方便的线程池。线程池的应用范围很广泛,比如我们熟悉的AsyncTask就是对线程池的封装。
在深入了解线程池的种类之前,我们先来看一下初始化线程池需要的参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
我们稍后再来解释各个参数代表的意思,先看看其他几个构造函数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中corePoolSize表示的是核心线程数。在线程池新建线程的时候,如果当前线程数量<
corePoolSize,则新建的是核心线程,否则就是非核心线程。核心线程默认即使啥也不干也会保留在线程池中,除非我们设置allowCoreThreadTimeOut为true,这样的话闲置的核心线程在超过keepAliveTime这个时间还没有干活的话就会被回收。那keepAliveTime的作用也解释清楚了,TimeUnit用来作为描述它的单位。而maximumPoolSize则表示线程池的线程总数,因为我们不仅有核心线程,还有非核心线程。ThreaFactory是创建线程使用的工厂类,一般我们用不到。RejectedExecutionHandler 是处理问题的策略,系统给我们提供了四个实现。ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常, ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常, ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。 ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
接下来就是比较重要的一个参数了,BlockingQueue。这是线程池的任务队列,当核心线程都在处理任务时,新添加的任务会被添加到队列中等待处理,如果队列满了,就会新建非核心线程来执行任务。我们来看一下常用的几个队列的实现:
-
SynchronousQueue
这个queue不会保留任何任务,接收到新任务时会立即提交给线程处理,如果所有线程都在工作就新建线程来处理,直到线程池满了发生错误。
一般我们在使用它的时候要把maximumPoolSize设得大一些,比如Integer.MAX_VALUE。 -
LinkedBlockingQueue
这个queue接收到任务时,如果线程数小于corePollSize,则新建核心线程处理任务,不然就入队等候。这个queue没有最大值限制,而正在工作的最多就corePoolSize个线程,maximumPololSize不会起作用。 -
ArrayBlockingQueue
这个queue接收到任务时,如果线程数小于corePollSize,则新建核心线程处理任务,不然就入队等候。如果队列满了则新建非核心线程处理任务,如果线程池跟队列都满了就会报错。 -
DelayQueue
这个queue里面的item必须实现Delayed接口,接收到任务后先入队,达到指定延时时间后才会执行任务。
好了,扯了这么久,大家不必对这些参数感到恐惧。事实上几乎很少需要我们自己配置这些参数的情况。Java为我们提供了四种封装实现,了解这些参数的含义有助于我们理解这四种封装的实现。
-
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
看到了我们熟悉的SynchronousQueue以及Integer.MAX_VALUE,我们大概就能猜到,得到的线程池有线程数无限制,有空闲线程则复用空闲线程,若无空闲线程则新建线程以及减少频繁创建/销毁线程,减少系统开销等优点。
-
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
想必你也想到了,得到的线程池并发数可控(nThreads),并且可以添加很多任务。
-
ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
这个没什么好解释的,定时任务。
-
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这个跟上面的FixedThreadPool有点类似,只不过这个是单线程而已。
我们自己需要定制一个线程池的话需要做多方面的考量,参数的设置非常影响程序的性能,大家在使用线程池前,一定要理解这些参数的含义!
关注一下吧