传统线程中,一个线程只能运行一次run方法,且运行结束后就被回收了,不能重复利用。如果想运行多个任务,则多少个任务就需要多少个线程,十分不便。此外,线程在运行中随着调度来回切换,易导致资源崩溃。因此,JDK提供了线程池。

线程池(Thread Pool)是池化技术的一种。线程池能够重用线程,降低系统资源消耗,方便管控线程并发数,实现线程的延时和定时功能。

池化技术:不直接创建具体的资源,而是创建一个资源对应的池,在池中创建具体的资源。池会让空闲的资源执行任务。当任务执行完毕后,资源不会被销毁,而是停留在池中,等待下一次任务。池能够重用资源,降低系统资源消耗,方便管理。除了线程池以外,常见的池化技术还包括数据库连接池等。

线程池的本质就是一些空闲的线程和一个任务队列(即一系列实现了Runnable接口、重写了run方法的类对象)。线程池中的空闲的线程会主动执行队列中的任务。任务执行完毕后,核心线程不会被销毁,而是等待下一个任务的执行。只要不断往队列中添加任务,有限的线程就能够执行无穷的任务。

一个线程池

通过创建一个线程池对象从而获得一个线程池。线程池类ThreadPoolExecutor实现了ExecutorService接口。

  1. /*
  2. * 线程池执行器ThreadPoolExecutor的构造器每个参数的含义依次是:
  3. * corePoolSize 核心线程数量
  4. * maximumPoolSize 最大线程的数量
  5. * keepAliveTime 线程池中除核心线程外的其他线程最长保留时间
  6. * util 时间的单位
  7. * workQueue 任务等待队列
  8. */
  9. ExecutorService excutor = new ThreadPoolExecutor(3, 6, 2,
  10. TimeUnit.SECONDS,
  11. new SynchronousQueue<Runnable>()); // 获得一个3核心,至多6线程的线程池
  12. excutor.execute(new Runnable() {
  13. public void run() { /******任务******/ }
  14. }); // 为线程池添加一个任务
  15. for (int i = 0; i < 20; i++) {
  16. excutor.execute(new Runnable() {
  17. public void run() { /******任务******/ }
  18. });
  19. } // 为线程池添加20个任务
  20. excutor.shutdown(); // 待所有线程空闲后,关闭线程池

可以向线程池提交任务,将任务添加到队列中。一旦存在空闲的线程,就会执行队列中的任务。非核心线程空闲后,待保留时间结束后即回收。核心线程不会被回收。收到shutdown()命令后,线程池会等待所有线程空闲后,才关闭线程池。

任务队列

不同的任务队列拥有不同的特性。

  • BlockingQueue:(继承了Queue的接口)双缓冲队列,内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

    • ArrayBlockingQueue:采用数组实现,具有固定长度的队列。使用构造器时须指定长度。对象采用FIFO排序。
    • LinkedBlockingQueue:采用双向链表实现,可指定大小,默认为Integer.MAX_VALUE。对象采用FIFO排序。
    • PriorityBlockingQueue:对象依据自然排序或比较器排序的LinkedBlockingQueue。
    • SynchronousQueue:是最快的任务队列。队列中只有一个元素,每次删除操作都要等待插入操作,每次插入操作都要等待删除操作。一旦有了插入线程和移除线程,那么很快由插入线程移交给移除线程。这个队列相当于一个通道,本身并不存储元素。

      JDK提供的线程池

      在实际项目中,一般在使用线程时均会使用线程池,而使用线程池则一般使用JDK提供的线程池。
      Java通过java.util.current.Executors类提供四种线程池(静态方法,返回一个线程池对象):
  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程;若所有线程均处于运行状态,则新建线程。该线程池创建的都是非核心线程,最大线程数为Integer.MAX_VALUE ,空闲线程存活时间为1分钟,采用最快的SynchronousQueue作为任务队列。可缓存线程池通常用于执行一些生存期很短的异步型任务。

  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。所有线程都是核心线程,采用LinkedBlockingQueue作为任务队列。线程的数量最好根据CPU能够并行执行的线程数(可通过Runtime.getRuntime().availableProcessors()获取)进行配置。
  • newSingleThreadExecutor:创建一个单线程的线程池,它只会用唯一的核心线程来执行任务,保证所有任务按照指定任务队列的顺序执行。采用LinkedBlockingQueue作为任务队列。
  • newScheduledThreadPool:创建一个定长线程池,并支持线程进行定时和周期性任务执行。采用DelayedWorkQueue作为任务队列,并拥有自己单独的类ScheduledExecutorService(ExecutorService的子类)。
    1. Runnable r = new Runnable() {
    2. public void run() {
    3. System.out.println(Thread.currentThread().getName());
    4. }
    5. }; // 定义一个任务
    6. ExecutorService cachedExe = Executors.newCachedThreadPool(); // 缓存线程池
    7. ExecutorService fixedExe = Executors.newFixedThreadPool(5); // 定长线程池,长度为5
    8. ExecutorService singleExe = Executors.newSingleThreadExecutor(); // 单线程池
    9. for (int i = 0; i < 10; i++) {
    10. cachedExe.execute(r);
    11. }
    12. for (int i = 0; i < 10; i++) {
    13. fixedExe.execute(r);
    14. }
    15. for (int i = 0; i < 10; i++) {
    16. singleExe.execute(r);
    17. }
    18. ScheduledExecutorService sexe = Executors.newScheduledThreadPool(4); // 定时线程池,长度为4
    19. sexe.schedule(r, 3, TimeUnit.SECONDS); // 3秒后执行r任务
    20. sexe.scheduleAtFixedRate(r, 3, 2, TimeUnit.SECONDS); // 3秒后执行r任务,随后每2秒执行一次
    前三个循环可能的运行结果如下:
cachedExe fixedExe singleExe
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-6
pool-1-thread-4
pool-1-thread-5
pool-1-thread-7
pool-1-thread-6
pool-2-thread-1
pool-2-thread-2
pool-2-thread-3
pool-2-thread-3
pool-2-thread-4
pool-2-thread-2
pool-2-thread-1
pool-2-thread-2
pool-2-thread-4
pool-2-thread-5
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1
pool-3-thread-1

可以看到,当池中的线程均忙时,cachedExe会随着任务的执行而不断创建新线程;fixedExe则最多使用5个线程;而singleExe则只有1个线程。