Java 线程池

42 0 0 0

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

线程池创建方式

  • 通过 ThreadPoolExecutor 手动创建线程池
  • 通过 Executors 执行器自动创建线程池。

而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:

  • Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  • Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  • Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  • Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  • Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  • Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  • ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。
    接下来我们分别来看这 7 种线程池的具体使用。

FixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:

1
2
3
4
5
6
7
8
public static void fixedThreadPool() { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 执行任务 threadPool.execute(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); }); }

CachedThreadPool

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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) { } }); } }

CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。

SingleThreadExecutor

创建单个线程的线程池,它可以保证先进先出的执行顺序。
SingleThreadExecutor 使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 个:

  • 可以复用线程:即使是单个线程池,也可以复用线程。
  • 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。

ScheduledThreadPool

创建一个可以执行延迟任务的线程池。
使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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); }

SingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。
它的使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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); }

newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。
newWorkStealingPool 使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()) { } }

ThreadPoolExecutor

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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(); } }); } }

参数解释:

  • corePoolSize: 线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。

  • maximumPoolSize: 最大线程数,当线程不够时能够创建的最大线程数(包含核心线程数) 临时线程数 = 最大线程数 - 核心线程数

  • keepAliveTime: 线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。

  • unit: 配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。

  • workQueue: 线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。

  • threadFactory: 为线程池提供创建新线程的线程工厂。

  • rejectedExecutionHandler: 线程池任务队列超过最大值之后的拒绝策略, RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution方法,可在此方法内添加任务超出最大值的事件处理;

ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:

  • DiscardPolicy():丢弃掉该任务但是不抛出异常,不推荐这种(导致使用者没觉察情况发生)
  • DiscardOldestPolicy():丢弃队列中等待最久的任务,然后把当前任务加入队列中。
  • AbortPolicy():丢弃任务并抛出 RejectedExecutionException 异常(默认)。
  • CallerRunsPolicy():由主线程负责调用任务的run()方法从而绕过线程池直接执行,既不抛弃 任务也不抛出异常(当最大线程数满了,任务队列中也满了,再来一个任 务,由主线程执行)
目录