线程池初探

2018-08-02  本文已影响6人  小小小小小粽子

日常开发中,我们经常会有一些任务需要在额外的线程中执行。尤其是在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。这是线程池的任务队列,当核心线程都在处理任务时,新添加的任务会被添加到队列中等待处理,如果队列满了,就会新建非核心线程来执行任务。我们来看一下常用的几个队列的实现:

  1. SynchronousQueue
    这个queue不会保留任何任务,接收到新任务时会立即提交给线程处理,如果所有线程都在工作就新建线程来处理,直到线程池满了发生错误。
    一般我们在使用它的时候要把maximumPoolSize设得大一些,比如Integer.MAX_VALUE。

  2. LinkedBlockingQueue
    这个queue接收到任务时,如果线程数小于corePollSize,则新建核心线程处理任务,不然就入队等候。这个queue没有最大值限制,而正在工作的最多就corePoolSize个线程,maximumPololSize不会起作用。

  3. ArrayBlockingQueue
    这个queue接收到任务时,如果线程数小于corePollSize,则新建核心线程处理任务,不然就入队等候。如果队列满了则新建非核心线程处理任务,如果线程池跟队列都满了就会报错。

  4. DelayQueue
    这个queue里面的item必须实现Delayed接口,接收到任务后先入队,达到指定延时时间后才会执行任务。

好了,扯了这么久,大家不必对这些参数感到恐惧。事实上几乎很少需要我们自己配置这些参数的情况。Java为我们提供了四种封装实现,了解这些参数的含义有助于我们理解这四种封装的实现。

  1. CachedThreadPool

    public static ExecutorService newCachedThreadPool() { 
    return  new ThreadPoolExecutor(
      0,
    Integer.MAX_VALUE, 
    60L, 
    TimeUnit.SECONDS, 
    new SynchronousQueue<Runnable>()); }
    
 看到了我们熟悉的SynchronousQueue以及Integer.MAX_VALUE,我们大概就能猜到,得到的线程池有线程数无限制,有空闲线程则复用空闲线程,若无空闲线程则新建线程以及减少频繁创建/销毁线程,减少系统开销等优点。
  1. FixedThreadPool

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

    想必你也想到了,得到的线程池并发数可控(nThreads),并且可以添加很多任务。

  2. 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()); }
    

    这个没什么好解释的,定时任务。

  3. SingleThreadExecutor

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

这个跟上面的FixedThreadPool有点类似,只不过这个是单线程而已。

我们自己需要定制一个线程池的话需要做多方面的考量,参数的设置非常影响程序的性能,大家在使用线程池前,一定要理解这些参数的含义!


关注一下吧
上一篇下一篇

猜你喜欢

热点阅读