线程池
1.为什么使用线程池
线程池的优势:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从对列中取出任务来执行。
它的主要特点为:线程复用;控制最大并发数;管理线程。
2.使用 Executors 工具类创建线程池常用的几种方式
方式一:newFixedThreadPool(int)
执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);//银行网点3个窗口
try {
for (int i = 1 ; i <= 10;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 号业务员办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
打印结果:
方式二:newSingleThreadExecutor()
一个任务一个任务的执行,一池一线程
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();//银行网点1个窗口
try {
for (int i = 1 ; i <= 10;i++){
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 号业务员办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool2.shutdown();
}
}
}
打印结果:(效率低)
方式三:newCachedThreadPool()
执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建可用时将重用它们,可扩容,遇强则强
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool3 = Executors.newCachedThreadPool();//银行网点可扩展窗口
try {
for (int i = 1 ; i <= 30;i++){
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 号业务员办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool3.shutdown();
}
}
}
打印结果:(每次执行结果可能都不一样,此次运行结果只执行了12个线程就完成了30个的任务)
方式四:newScheduledThreadPool()
创建一个定长线程池,支持定时及周期性任务执行。
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ScheduledExecutorService threadPool4 = Executors.newScheduledThreadPool(5);//银行网点可定时窗口
try {
for (int i = 1 ; i <= 10;i++){
threadPool4.schedule(()->{
System.out.println(Thread.currentThread().getName() + "\t 号业务员办理业务");
},3,TimeUnit.SECONDS);
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool4.shutdown();
}
}
}
执行结果(表示延迟3秒执行):
3.线程池几个重要参数
3.1 核心底层源码
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
3.2 七大参数
- ① corePoolSize:线程池中的常驻核心线程数
- ② maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
- ③ keepAliveTime:多余的空闲线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
- ④ unit:keepAliveTime的单位
- ⑤ workQueue:任务队列,被提交但尚未被执行的任务
- ⑥ threadFatory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
- ⑦ handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略
4.线程池底层工作原理
4.1 线程底层工作原理图
4.2 文字说明
① 在创建了线程池后,线程池中的线程数为零
② 当调用 execute() 方法添加一个请求任务时,线程池会做出如下判断:
a、如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b、如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
c、如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务(“这个任务”指的是新的任务,而非队列里的任务);
d、如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
③、当一个线程完成任务时,它会从队列中取下一个任务来执行
④、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
4.3 在工作中该三种创建线程池的方式都不使用
FixedThreadPool 和 SingleThreadPool
CachedThreadPool
5. 线程池的拒绝策略
5.1 什么情况下使用拒绝策略
等待队列已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务。这个时候我们就需要拒绝策略机制合理的处理这个问题。
5.2 四种拒绝策略
6.自定义线程池
例子:
package com.test.demo1;
import java.util.concurrent.*;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()//默认的拒绝策略
);
try {
for (int i = 1 ; i <= 9;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 号业务员办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
打印结果:(当for循环里 i <= 8 时,首先执行两个任务(2个核心线程数),再将三个任务放进阻塞队列中(3个队列的长度),然后当队列中放满时,再根据最大线程数(5个最大线程数)执行剩余的三个任务,当超过核心线程数和一定数量的队列长度且达到最大线程数时,就会报异常(选择的是默认拒绝策略:AbortPolicy)。注:每一次执行的结果可能都一样,有时不会出现报错)
【java.util.concurrent.RejectedExecutionException: Task com.test.demo1.MyThreadPoolDemo$$Lambda
1
/
1078694789
@
3
b
9
a
45
b
3
r
e
j
e
c
t
e
d
f
r
o
m
j
a
v
a
.
u
t
i
l
.
c
o
n
c
u
r
r
e
n
t
.
T
h
r
e
a
d
P
o
o
l
E
x
e
c
u
t
o
r
@
7699
a
589
[
R
u
n
n
i
n
g
,
p
o
o
l
s
i
z
e
=
5
,
a
c
t
i
v
e
t
h
r
e
a
d
s
=
0
,
q
u
e
u
e
d
t
a
s
k
s
=
0
,
c
o
m
p
l
e
t
e
d
t
a
s
k
s
=
8
]
a
t
j
a
v
a
.
u
t
i
l
.
c
o
n
c
u
r
r
e
n
t
.
T
h
r
e
a
d
P
o
o
l
E
x
e
c
u
t
o
r
1/1078694789@3b9a45b3 rejected from java.util.concurrent.ThreadPoolExecutor@7699a589[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8] at java.util.concurrent.ThreadPoolExecutor
1/1078694789@3b9a45b3rejectedfromjava.util.concurrent.ThreadPoolExecutor@7699a589[Running,poolsize=5,activethreads=0,queuedtasks=0,completedtasks=8]atjava.util.concurrent.ThreadPoolExecutorAbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.test.demo1.MyThreadPoolDemo.main(MyThreadPoolDemo.java:18)】
拒绝策略模式改为 new ThreadPoolExecutor.CallerRunsPolicy()。
打印的结果为:
拒绝策略模式改为 new ThreadPoolExecutor.DiscardOldestPolicy()。
打印的结果为:
拒绝策略模式改为new ThreadPoolExecutor.DiscardPolicy()。
打印的结果为: