乐趣区

关于java:面试突击28线程池有几种创建方式推荐使用哪种

在 Java 语言中,并发编程都是通过创立线程池来实现的,而线程池的创立形式也有很多种,每种线程池的创立形式都对应了不同的应用场景,总体来说线程池的创立能够分为以下两类:

  • 通过 ThreadPoolExecutor 手动创立线程池。
  • 通过 Executors 执行器主动创立线程池。


而以上两类创立线程池的形式,又有 7 种具体实现办法,这 7 种实现办法别离是:

  1. Executors.newFixedThreadPool:创立一个固定大小的线程池,可管制并发的线程数,超出的线程会在队列中期待。
  2. Executors.newCachedThreadPool:创立一个可缓存的线程池,若线程数超过解决所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创立单个线程数的线程池,它能够保障先进先出的执行程序。
  4. Executors.newScheduledThreadPool:创立一个能够执行提早工作的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创立一个单线程的能够执行提早工作的线程池。
  6. Executors.newWorkStealingPool:创立一个抢占式执行的线程池(工作执行程序不确定)【JDK 1.8 增加】。
  7. ThreadPoolExecutor:手动创立线程池的形式,它创立时最多能够设置 7 个参数。

接下来咱们别离来看这 7 种线程池的具体应用。

1.FixedThreadPool

创立一个固定大小的线程池,可管制并发线程数。
应用 FixedThreadPool 创立 2 个固定大小的线程池,具体实现代码如下:

public static void fixedThreadPool() {
    // 创立 2 个线程的线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);

    // 创立工作
    Runnable runnable = new Runnable() {
        @Override
        public void run() {System.out.println("工作被执行, 线程:" + Thread.currentThread().getName());
        }
    };

    // 线程池执行工作(一次增加 4 个工作)
    // 执行工作的办法有两种:submit 和 execute
    threadPool.submit(runnable);  // 执行形式 1:submit
    threadPool.execute(runnable); // 执行形式 2:execute
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

以上程序的执行后果如下图所示:

如果感觉以上办法比拟繁琐,还用应用以下简略的形式来实现线程池的创立和应用:

public static void fixedThreadPool() {
    // 创立线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    // 执行工作
    threadPool.execute(() -> {System.out.println("工作被执行, 线程:" + Thread.currentThread().getName());
    });
}

2.CachedThreadPool

创立一个可缓存的线程池,若线程数超过工作所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。
CachedThreadPool 应用示例如下:

public static void cachedThreadPool() {
    // 创立线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();
    // 执行工作
    for (int i = 0; i < 10; i++) {threadPool.execute(() -> {System.out.println("工作被执行, 线程:" + Thread.currentThread().getName());
            try {TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {}});
    }
}

以上程序的执行后果如下图所示:

从上述后果能够看出,线程池创立了 10 个线程来执行相应的工作。

应用场景

CachedThreadPool 是依据短时间的任务量来决定创立的线程数量的,所以它适宜短时间内有突发大量工作的解决场景。

3.SingleThreadExecutor

创立单个线程的线程池,它能够保障先进先出的执行程序。
SingleThreadExecutor 应用示例如下:

public static void singleThreadExecutor() {
    // 创立线程池
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    // 执行工作
    for (int i = 0; i < 10; i++) {
        final int index = i;
        threadPool.execute(() -> {System.out.println(index + ": 工作被执行");
            try {TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {}});
    }
}

以上程序的执行后果如下图所示:

单个线程的线程池有什么意义?

单个线程的线程池相比于线程来说,它的长处有以下 2 个:

  • 能够复用线程:即便是单个线程池,也能够复用线程。
  • 提供了工作治理性能:单个线程池也领有工作队列,在工作队列能够存储多个工作,这是线程无奈实现的,并且当工作队列满了之后,能够执行回绝策略,这些都是线程不具备的。

    4.ScheduledThreadPool

    创立一个能够执行提早工作的线程池。
    应用示例如下:

    public static void scheduledThreadPool() {
      // 创立线程池
      ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
      // 增加定时执行工作(1s 后执行)
      System.out.println("增加工作, 工夫:" + new Date());
      threadPool.schedule(() -> {System.out.println("工作被执行, 工夫:" + new Date());
          try {TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {}}, 1, TimeUnit.SECONDS);
    }

    以上程序的执行后果如下图所示:

    从上述后果能够看出,工作在 1 秒之后被执行了,实现了提早 1s 再执行工作。

    5.SingleThreadScheduledExecutor

    创立一个单线程的能够执行提早工作的线程池,此线程池能够看作是 ScheduledThreadPool 的单线程池版本。
    它的应用示例如下:

    public static void SingleThreadScheduledExecutor() {
      // 创立线程池
      ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
      // 增加定时执行工作(2s 后执行)
      System.out.println("增加工作, 工夫:" + new Date());
      threadPool.schedule(() -> {System.out.println("工作被执行, 工夫:" + new Date());
          try {TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {}}, 2, TimeUnit.SECONDS);
    }

    以上程序的执行后果如下图所示:

    从上述后果能够看出,工作在 2 秒之后被执行了。

    6.newWorkStealingPool

    创立一个抢占式执行的线程池(工作执行程序不确定),此办法是 JDK 1.8 版本新增的,因而只有在 JDK 1.8 以上的程序中能力应用。
    newWorkStealingPool 应用示例如下:

    public static void workStealingPool() {
      // 创立线程池
      ExecutorService threadPool = Executors.newWorkStealingPool();
      // 执行工作
      for (int i = 0; i < 10; i++) {
          final int index = i;
          threadPool.execute(() -> {System.out.println(index + "被执行, 线程名:" + Thread.currentThread().getName());
          });
      }
      // 确保工作执行实现
      while (!threadPool.isTerminated()) {}}

    以上程序的执行后果如下图所示:

    从上述后果能够看出,工作的执行程序是不确定的,因为它是抢占式执行的。

    7.ThreadPoolExecutor

    ThreadPoolExecutor 是最原始、也是最举荐的手动创立线程池的形式,它在创立时最多提供 7 个参数可供设置。
    ThreadPoolExecutor 应用示例如下:

    public static void myThreadPoolExecutor() {
      // 创立线程池
      ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
      // 执行工作
      for (int i = 0; i < 10; i++) {
          final int index = i;
          threadPool.execute(() -> {System.out.println(index + "被执行, 线程名:" + Thread.currentThread().getName());
              try {Thread.sleep(1000);
              } catch (InterruptedException e) {e.printStackTrace();
              }
          });
      }
    }

    以上程序的执行后果如下图所示:

    ThreadPoolExecutor 相比于其余创立线程池的劣势在于,它能够通过参数来管制最大工作数和回绝策略,让线程池的执行更加通明和可控,所以在阿里巴巴《Java 开发手册》是这样规定的:

    【强制要求】线程池不容许应用 Executors 去创立,而是通过 ThreadPoolExecutor 的形式,这样的解决形式让写的同学更加明确线程池的运行规定,躲避资源耗尽的危险。

阐明:Executors 返回的线程池对象的弊病如下:

1)FixedThreadPool 和 SingleThreadPool:容许的申请队列长度为 Integer.MAX_VALUE,可能会沉积大量的申请,从而导致 OOM。

2)CachedThreadPool:容许的创立线程数量为 Integer.MAX_VALUE,可能会创立大量的线程,从而导致 OOM。

总结

线程池的创立形式总共有以下 7 种:

  1. Executors.newFixedThreadPool:创立一个固定大小的线程池,可管制并发的线程数,超出的线程会在队列中期待。
  2. Executors.newCachedThreadPool:创立一个可缓存的线程池,若线程数超过解决所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创立单个线程数的线程池,它能够保障先进先出的执行程序。
  4. Executors.newScheduledThreadPool:创立一个能够执行提早工作的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创立一个单线程的能够执行提早工作的线程池。
  6. Executors.newWorkStealingPool:创立一个抢占式执行的线程池(工作执行程序不确定)【JDK 1.8 增加】。
  7. ThreadPoolExecutor:手动创立线程池的形式,它创立时最多能够设置 7 个参数。

线程池的创立举荐应用最初一种 ThreadPoolExecutor 的形式来创立,因为应用它能够明确线程池的运行规定,躲避资源耗尽的危险

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java 面试真题解析

面试合集:https://gitee.com/mydb/interview

退出移动版