线程池的意义

在Java编程中,总是容易碰到多线程并发的场景。通常最简单的方法,就是new Thread(runnable)方式来创建一条线程。但是如果并发量大,且业务长期需要并发操作,那这个方法就行不通了。首先线程数量创建太多,太占用资源,甚至会超过系统的线程数量限制导致异常。其次,这种方式创建线程,执行完runnable后就会销毁线程,下次并发任务到达又要创建新的线程。频繁的“销毁-创建”的操作也会浪费资源。

所以就有了线程池的概念,线程池本质就是将创建的线程缓存起来,等并发任务到达,就直接用缓存的线程执行。线程池的有点:

  • 降低重复创建线程的开销。
  • 提高响应速度。任务到达时,可以不需要等到线程创建就能立即执行。
  • 方便管理线程。
Executors

我们平时java创建线程池,最常见的方法就是使用Executors中提供的一系列工厂方法。比如:

  • newSingleThreadExecutor:创建只有一条线程的线程池。这个条线程会一直存活下去( 除非线程池被销毁)。内部维持着一个线性队列,如果有任务到达,会先进入队列,工作线程会依次从队列中取出任务执行。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory));
      }
  • newCachedThreadPool:这个方法会创建不限制线程数量的线程池。只要有新任务到达,如果没有闲置的线程,就会创建新的线程来执行该任务。 如果线程闲置时间超过60秒后,就会被回收。
public static ExecutorService newCachedThreadPool() {
     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>());
   }
  • newFixedThreadPool:创建一个执行数量数量的线程池。这些线程会一直存活下去。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory);
    }

细心的朋友可能发现了,这三个方法,都是简单创建了一个ThreadPoolExecutor的对象而已。其实真正的核心类就是这个ThreadPoolExecutor。

ThreadPoolExecutor

这个类就是一个线程池任务执行对象。看它的构造方法签名:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
         .........
    }

我们来逐个解析各个参数的意义:

  • corePoolSize: 核心线程数量。所谓核心线程,就是默认长期存活的线程,不会被回收(也就意味这长期占用资源)。一般如果业务需要,经常需要并发,那就定义合适数目的核心线程,避免线程被回收又再创建新线程带来的开销。
  • maximumPoolSize:最大线程数量。如果核心线程不够用,会增加新的临时线程。这个maximumPoolSize就是总的线程数量。所以这个值不能小于corePoolSize,否则会抛异常。
  • keepAliveTime:临时创建的线程的最大闲置时间。如果临时线程闲置时间超过这个值,就会被终止。如果线程池一直闲置,最终线程数会削减至corePoolSize(因为核心线程会一直存活)。
  • unit:时间单位,可以是小时、分钟、秒等。
  • workQueue:任务队列容器。存放尚未来得及执行任务。比方说,如果核心线程不够用了,任务会先进入这个队列,等待新的线程创建后(或者其他任务执行完)来执行它。
  • handler:任务被拒绝时的回调。比方说,当线程数量达到maximumPoolSize且workQueue也达到最大容量了。此时任务无法被执行,只能被拒绝,这时会回调这个方法。这个参数不传的话,默认的实现是抛出一个RejectedExecutionException

这样其实,我们可以不用Executors工具方法,可以根据自己的业务需要,创建更合适的线程池。