在 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);
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();
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()方法从而绕过线程池直接执行,既不抛弃 任务也不抛出异常(当最大线程数满了,任务队列中也满了,再来一个任 务,由主线程执行)
<p>在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:</p>
<h1><a id="_2"></a>线程池创建方式</h1>
<ul>
<li>通过 ThreadPoolExecutor 手动创建线程池</li>
<li>通过 Executors 执行器自动创建线程池。</li>
</ul>
<p>而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:</p>
<ul>
<li>Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。</li>
<li>Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。</li>
<li>Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。</li>
<li>Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。</li>
<li>Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。</li>
<li>Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。</li>
<li>ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。<br />
接下来我们分别来看这 7 种线程池的具体使用。</li>
</ul>
<h1><a id="FixedThreadPool_17"></a>FixedThreadPool</h1>
<p>创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。<br />
使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">fixedThreadPool</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ExecutorService threadPool = Executors.newFixedThreadPool(<span class="hljs-number">2</span>);
<span class="hljs-comment">// 执行任务</span>
threadPool.execute(() -> {
System.out.println(<span class="hljs-string">"任务被执行,线程:"</span> + Thread.currentThread().getName());
});
}
</code></div></pre>
<h1><a id="CachedThreadPool_31"></a>CachedThreadPool</h1>
<p>创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。<br />
CachedThreadPool 使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">cachedThreadPool</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ExecutorService threadPool = Executors.newCachedThreadPool();
<span class="hljs-comment">// 执行任务</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) {
threadPool.execute(() -> {
System.out.println(<span class="hljs-string">"任务被执行,线程:"</span> + Thread.currentThread().getName());
<span class="hljs-keyword">try</span> {
TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
}
});
}
}
</code></div></pre>
<blockquote>
<p>CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。</p>
</blockquote>
<h1><a id="SingleThreadExecutor_52"></a>SingleThreadExecutor</h1>
<p>创建单个线程的线程池,它可以保证先进先出的执行顺序。<br />
SingleThreadExecutor 使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">singleThreadExecutor</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ExecutorService threadPool = Executors.newSingleThreadExecutor();
<span class="hljs-comment">// 执行任务</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) {
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> index = i;
threadPool.execute(() -> {
System.out.println(index + <span class="hljs-string">":任务被执行"</span>);
<span class="hljs-keyword">try</span> {
TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
}
});
}
}
</code></div></pre>
<p>单个线程的线程池相比于线程来说,它的优点有以下 2 个:</p>
<ul>
<li>可以复用线程:即使是单个线程池,也可以复用线程。</li>
<li>提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。</li>
</ul>
<h1><a id="ScheduledThreadPool_79"></a>ScheduledThreadPool</h1>
<p>创建一个可以执行延迟任务的线程池。<br />
使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scheduledThreadPool</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(<span class="hljs-number">5</span>);
<span class="hljs-comment">// 添加定时执行任务(1s 后执行)</span>
System.out.println(<span class="hljs-string">"添加任务,时间:"</span> + <span class="hljs-keyword">new</span> Date());
threadPool.schedule(() -> {
System.out.println(<span class="hljs-string">"任务被执行,时间:"</span> + <span class="hljs-keyword">new</span> Date());
<span class="hljs-keyword">try</span> {
TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
}
}, <span class="hljs-number">1</span>, TimeUnit.SECONDS);
}
</code></div></pre>
<h1><a id="SingleThreadScheduledExecutor_97"></a>SingleThreadScheduledExecutor</h1>
<p>创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。<br />
它的使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SingleThreadScheduledExecutor</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
<span class="hljs-comment">// 添加定时执行任务(2s 后执行)</span>
System.out.println(<span class="hljs-string">"添加任务,时间:"</span> + <span class="hljs-keyword">new</span> Date());
threadPool.schedule(() -> {
System.out.println(<span class="hljs-string">"任务被执行,时间:"</span> + <span class="hljs-keyword">new</span> Date());
<span class="hljs-keyword">try</span> {
TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
}
}, <span class="hljs-number">2</span>, TimeUnit.SECONDS);
}
</code></div></pre>
<h1><a id="newWorkStealingPool_115"></a>newWorkStealingPool</h1>
<p>创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。<br />
newWorkStealingPool 使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">workStealingPool</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ExecutorService threadPool = Executors.newWorkStealingPool();
<span class="hljs-comment">// 执行任务</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) {
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> index = i;
threadPool.execute(() -> {
System.out.println(index + <span class="hljs-string">" 被执行,线程名:"</span> + Thread.currentThread().getName());
});
}
<span class="hljs-comment">// 确保任务执行完成</span>
<span class="hljs-keyword">while</span> (!threadPool.isTerminated()) {
}
}
</code></div></pre>
<h1><a id="ThreadPoolExecutor_135"></a>ThreadPoolExecutor</h1>
<p>ThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置<br />
ThreadPoolExecutor 使用示例如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">myThreadPoolExecutor</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 创建线程池</span>
ThreadPoolExecutor threadPool = <span class="hljs-keyword">new</span> ThreadPoolExecutor(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">100</span>, TimeUnit.SECONDS, <span class="hljs-keyword">new</span> LinkedBlockingQueue<>(<span class="hljs-number">10</span>));
<span class="hljs-comment">// 执行任务</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) {
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> index = i;
threadPool.execute(() -> {
System.out.println(index + <span class="hljs-string">" 被执行,线程名:"</span> + Thread.currentThread().getName());
<span class="hljs-keyword">try</span> {
Thread.sleep(<span class="hljs-number">1000</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
e.printStackTrace();
}
});
}
}
</code></div></pre>
<p>参数解释:</p>
<ul>
<li>
<p>corePoolSize: 线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。</p>
</li>
<li>
<p>maximumPoolSize: 最大线程数,当线程不够时能够创建的最大线程数(包含核心线程数) 临时线程数 = 最大线程数 - 核心线程数</p>
</li>
<li>
<p>keepAliveTime: 线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。</p>
</li>
<li>
<p>unit: 配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。</p>
</li>
<li>
<p>workQueue: 线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。</p>
</li>
<li>
<p>threadFactory: 为线程池提供创建新线程的线程工厂。</p>
</li>
<li>
<p>rejectedExecutionHandler: 线程池任务队列超过最大值之后的拒绝策略, RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution方法,可在此方法内添加任务超出最大值的事件处理;</p>
</li>
</ul>
<p>ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:</p>
<ul>
<li>DiscardPolicy():丢弃掉该任务但是不抛出异常,不推荐这种(导致使用者没觉察情况发生)</li>
<li>DiscardOldestPolicy():丢弃队列中等待最久的任务,然后把当前任务加入队列中。</li>
<li>AbortPolicy():丢弃任务并抛出 RejectedExecutionException 异常(默认)。</li>
<li>CallerRunsPolicy():由主线程负责调用任务的run()方法从而绕过线程池直接执行,既不抛弃 任务也不抛出异常(当最大线程数满了,任务队列中也满了,再来一个任 务,由主线程执行)</li>
</ul>