什么是池?
先讲一个例子,有可能可以帮助你理解,觉得无趣的小伙伴可以直接跳过。相信大多数都知道外包公司,甚至很多小伙伴还在外包公司呆过,其实外包公司我觉得也就可以看作是个"池"。
比如说小明最近离开了外包公司,自己带着几个兄弟组建了一个开发小组成立了个工作室并开始接项目,他们也开始了没日没夜的撸码干活,开足了马力同时干着三四个项目,忙的个不亦乐乎。
可是随着他们接的项目越来越多了,小明和小伙伴们熬夜熬的开始掉头发了,这样下去说不定哪天就栽倒在电脑桌下再也不能撸码了。于是他们一商量开了个外包公司A由小明负责,然后组建5个Java开发小组,不久后公司就接到了来自华为的一个外包项目需要3个开发小组,剩下两个小组待命,接着又接了一个阿里的外包项目需要3个开发小组,可是人员不够,本着向钱看,向厚看的人生信仰,小明赶紧招兵买马又扩充了一个开发小组,所有人都撸起袖子加油干,小明笑得很灿烂,数钱数到手抽筋。 过了段时间又接了个公司的项目,可是小明一看工期有点短,要是再扩招一个开发组那干完了接不上岂不是要白白养这么多人吗,一拍脑袋他想到个妙招,组建了一支兼职的队伍做完了就可以解散,要是又有短期的项目又可以把他们召集起来,小明开始在办公室悠闲地喝着茶,打着吃鸡。 过了几天来了个小公司的项目,钱少事多,小明翻了翻白眼直接给拒绝了,继续喝茶,吃鸡。。。
好了,这只是我在写总结的时候突然脑袋里冒出的一个虚拟故事,开始进入正题。
一、哪4个方法?
JDK8有新增方法newWorkStealingPool,在此不作讨论。
// 创建单个线程
Executors.newSingleThreadExecutor();
// 创建固定数量的线程
Executors.newFixedThreadPool(3);
// 可动态调整,随着请求的增多线程也随之创建
Executors.newCachedThreadPool();
// 用来调度即将执行的任务的线程池
Executors.newScheduledThreadPool();
newSingleThreadExecutor
无论调用多少次都是同一个线程,就像小明和小伙伴刚成立工作室,无论他们接了多少个项目都只有他们一个组没日没夜的干。
@Test
public void testNewSingleThreadExecutor() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 10; i++) {
executorService.execute(() -> System.out.println("当前线程=>" + Thread.currentThread().getName()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
打印出来显示只产生了一个线程。
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
newFixedThreadPool
固定地创建5个线程,当小明成立外包公司后有5个开发组,需要3个组的时候就调用出去3个组,如果需要6个那其中至少有一个组就必需多做一个项目。
@Test
public void testNewFixedThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i = 1; i <= 6; i++) {
executorService.execute(() -> System.out.println("当前线程=>" + Thread.currentThread().getName()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
线程pool-1-thread-1被打印了两次,当然每次执行的结果可能都是不同的,但其中有一个线程肯定会打印两次。
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-2
当前线程=>pool-1-thread-3
当前线程=>pool-1-thread-4
当前线程=>pool-1-thread-5
newCachedThreadPool
创建可伸缩的线程,好比小明组建的兼职开发,可以根据项目情况再组建两个兼职开发组。
@Test
public void testNewCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 10; i++) {
executorService.execute(() -> System.out.println("当前线程=>" + Thread.currentThread().getName()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
每次执行的打印结果都可能不同,可能是6个线程,也可能是7个。。。
当前线程=>pool-1-thread-1
当前线程=>pool-1-thread-2
当前线程=>pool-1-thread-3
当前线程=>pool-1-thread-4
当前线程=>pool-1-thread-5
当前线程=>pool-1-thread-6
当前线程=>pool-1-thread-3
当前线程=>pool-1-thread-5
当前线程=>pool-1-thread-6
当前线程=>pool-1-thread-4
newScheduledThreadPool
定时调度。
newScheduledThreadPool共计有三个方法:
schedule(commod,delay,unit) 系统启动后,需要等待多久执行,delay是等待时间。只执行一次,没有周期性。
scheduleAtFixedRate(commod,initialDelay,period,unit) 以period为固定周期时间,按照一定频率来重复执行任务,initialDelay是说系统启动后,需要等待多久才开始执行。例如:设置了period为5秒,线程启动之后执行大于5秒,线程结束之后,立即启动线程的下一次,如果线程启动之后只执行了3秒就结束了,那执行下一次需要等待2秒再执行。这个是优先保证任务执行的频率。
scheduleWithFixedDelay(commod,initialDelay,delay,unit) 以delay为固定延迟时间,按照一定的等待时间来执行任务,initialDelay意义与上面的相同。例如:设置了delay为5秒,线程启动之后不管执行了多久,结束之后都需要先过5秒,才能执行下一次。这个是优先保证任务执行的间隔。
以scheduleWithFixedDelay为例。
@Test
public void testScheduledThreadPool() {
ScheduledExecutorService scheduler = null;
try {
scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> scheduleTask =
scheduler.scheduleWithFixedDelay(
() -> System.out.println("run()"),
5,
1,
TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 一般池化技术都需要关闭资源
scheduler.shutdown();
}
}
建议
以上四种方法其实在工作中都基本使用不到,当然阿里的Java开发手册中强制规定不允许使用Executors方式来创建,而建议使用ThreadPoolExecutor的方式。(阿里巴巴Java开发手册2020)
二、又是哪7个参数?
ThreadPoolExecutor
我们分别点进各个方法的源码里面可以发现都是调用了ThreadPoolExecutor这个类来创建(也就是上面阿里强制使用的方法)。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newScheduledThreadPool是先调用了ScheduledThreadPoolExecutor,这个也是继承ScheduledThreadPoolExecutor,所以调用super也就还是用的ThreadPoolExecutor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
再去看ThreadPoolExecutor这个构造方法,这里的参数就是所说的的七个参数。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @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.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @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}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize 核心线程数,一直存活,即使线程数小于核心线程数且线程数有空闲,线程池也会创建新的线程。
maximumPoolSize 最大线程数,当线程数大于核心线程数并且任务队列已经满了的时候,线程池会创建新的线程,当线程数大于最大线程数并且任务队列已经满了,会抛出异常。
keepAliveTime 线程空闲时间,当线程的空闲时间达到keepAliveTime时,线程会退出,直到线程数等于核心线程数,可以设置参数allowCoreThreadTimeout=true,则会直到线程数为0。
TimeUnit unit 超时时间单位。
BlockingQueue workQueue 阻塞队列,任务队列的容量。
ThreadFactory threadFactory 线程工厂,基本不用设置(默认使用Executors.defaultThreadFactory())
RejectedExecutionHandler handler 拒绝策略,任务拒绝处理器。
三、哪4种拒绝策略?
可以在源码中看到默认用的是AbortPolicy。
/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
四种分为是:
AbortPolicy() 线程池满了,如果还有线程想加入,不处理这个请求,抛出异常。
CallerRunsPolicy() 哪来的回哪去
DiscardPolicy() 队列满了,丢掉任务,不会抛出异常。
DiscardOldestPolicy() 队列满了,尝试去和最早的竞争,不会抛出异常。
总结
其实线程池就是种池化技术,其他的比如还有数据库连接池,内存池,http连接池等等,这样可以减少资源对象的创建次数,垃圾回收的开销,提高程序的性能,特别是在高并发下这种提高更加明显。