1 Executo
服务端项目,经常需要处理执行时间很短而数目巨大的请求,如果为每一个请求创建一个新的线程,则会导致性能上的瓶颈。线程池核心原理是对线程对象进行管理,包括创建与销毁。
图 线程池相关UML图
1.1 Executors
ThreadPoolExecutor在实例化时需传入多个参数,还要考虑线程的并发数等与线程池运行效率有关的参数,所以官方建议使用Executors工厂类来创建线程池对象。
图 Executors类中的方法
1.1.1 newCachedThreadPool()
创建无界线程池。
public class UnboundedThreadPool {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 3; i++) {
executorService.execute(new MyRunnable());
}
TimeUnit.SECONDS.sleep(3);
for (int i = 0; i < 3; i++) {
executorService.execute(new MyRunnable());
}
}
}
/*
运行结果(程序还未终止):
pool-1-thread-3:1679829983418
pool-1-thread-1:1679829983417
pool-1-thread-2:1679829983418
pool-1-thread-1:1679829986430
pool-1-thread-2:1679829986431
pool-1-thread-3:1679829986432
*/
在线程池中,线程对象是可以复用的。
但是该方法创建无界线程池极易造成内存占用率大幅升高,导致内存溢出或者系统运行效率严重下降。
public class UnboundedThreadPool2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
}
图 无界线程池创建了超多的线程
1.1.2 newFixedThreadPool(int)
创建有界线程池,池中线程个数可以指定最大数量。
public class BoundedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 3000; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
图 有界线程池最多创建5个线程
2 ThreadPoolExecutor
2.1 构造方法
图 ThreadPoolExecutor构造方法
corePoolSize: 池中至少要保留的线程数,定义corePool核心池的大小
maximumPoolSize:池中运行的最大线程数。
keepAliveTime:当线程数量大于corePoolSize值时,在没有超过指定的时间内是不能从线程池中将空闲线程删除,如果超过此时间,则删除空闲线程。能删除的范围是corePool之外的线程。
unit: keepAliveTime参数的时间单位
workQueue:执行前用于保持任务的队列
threadFactory:线程工厂
handler: 池中的资源被全部占用时,对新添加的任务的处理策略
2.1.1 构造函数详解
核心线程数:C,运行最大线程数:M,要执行任务的线程:N。指定时间:T
(1)使用无参new LinkedBlockingQueue():特点是只使用核心池的线程执行任务。M,T参数设置无效。
N <= C | 立即在核心池中创建线程并运行,这些任务不会被放入LinkedBlockingQueue中。 |
N > C | 会把N-C部份放入LinkedBlockingQueue中等待核心池的线程执行。 |
表 无参LinkedBlockingQueue下不同任务数量的任务执行逻辑
(2)使用SynchronousQueue:不会把任务放入该队列中。
N <= C | 立即在核心线程池中创建线程并运行任务。 |
N > C && N <= M | 马上创建最多M个线程运行任务,N- C执行完任务后在超过T时,将N- C部份清除,如果N- C在T内未执行完任务,则在其完成任务后进行清除。 |
N > M | 最多处理M个任务,其他任务不再处理并抛出异常。 |
表 SynchronousQueue下不同任务数量的任务执行逻辑
(3)使用有参new LinkedBlockingQueue(X): X表示队列最大存储长度。特点是核心池中的线程和M- C线程可能一起执行任务。
N <= C | 立即在核心池中创建线程并运行任务。 |
N > C && (N-C) <= X | 立即在核心池中创建线程并运行任务,并把N- C放入队列中等待核心池中的线程执行。 |
N > C && (N-C) > X && N – C – X <= M - C | C + X 任务运行逻辑和上面一样。马上创建N – C – X个线程运行这些任务,清除逻辑和SynchronousQueue情况的一样。 |
N > C && N – C > X && N – C – X > M - C | 多出来的任务被拒绝并出现异常。 |
表 有参LinkedBlockingQueue下不同任务数量的任务执行逻辑
public class LinedBlockingQueuePool {
static final int CORE = 3, LINKED_SIZE = 2, MAX = 5;
static final long TIME = 2; // 任务清除超时时间
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(TIME + 2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
LinkedBlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(LINKED_SIZE);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE, MAX, TIME, TimeUnit.SECONDS, blockingQueue);
for(int i = 0; i < MAX + LINKED_SIZE + 3; i++) {
poolExecutor.execute(new MyRunnable());
}
}
}
/*
运行结果:
pool-1-thread-3:1679843594419
pool-1-thread-2:1679843594418
pool-1-thread-1:1679843594419
pool-1-thread-4:1679843594419
pool-1-thread-5:1679843594419
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.huangmingfu.day7.article.LinedBlockingQueuePool$MyRunnable@12a3a380 rejected from java.util.concurrent.ThreadPoolExecutor@29453f44[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.huangmingfu.day7.article.LinedBlockingQueuePool.main(LinedBlockingQueuePool.java:29)
pool-1-thread-4:1679843598424
pool-1-thread-1:1679843598424
*/
2.1.2 拒绝策略
AbortPolicy | 被拒绝时,抛出RejectedExecutionExeception异常,是线程池默认使用的拒绝策略。 |
CallerRunsPolicy | 被拒绝时,会让调用者来处理被拒绝的任务。 |
DiscardOldestPolicy | 被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。 |
DiscardPolicy | 线程池将丢弃被拒绝的任务。 |
表 ThreadPoolExecutor类中的四种拒绝策略
public class CallerRunsPolicyPool {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>(2);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 4, 3, TimeUnit.SECONDS, linkedBlockingQueue, new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
poolExecutor.execute(() -> {
System.out.println( "线程池中:" + Thread.currentThread().getName() + ":" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
/*
运行结果(程序未终止):
线程池中:pool-1-thread-1:1679844913803
线程池中:pool-1-thread-3:1679844913803
线程池中:pool-1-thread-2:1679844913803
线程池中:main:1679844913803
线程池中:pool-1-thread-4:1679844913803
线程池中:main:1679844918810
线程池中:pool-1-thread-3:1679844918810
线程池中:pool-1-thread-4:1679844918810
线程池中:pool-1-thread-3:1679844923813
线程池中:pool-1-thread-1:1679844923814
*/
2.2 shutdown()和shutdownNow()
shutdown():使当前未执行完成的任务继续执行,而队列中未执行的任务会继续执行,不删除队列中的任务,不再允许添加新的任务。该方法不会阻塞。
shutdownNow():使当前未执行完的任务继续执行,而队列中未执行的任务不再执行。删除队列中的任务,不再运行添加新的任务,该方法不会阻塞。