1、为什么要用线程池:
在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
2、线程池的七个参数
线程池类图
七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
任务提交
1、public void execute() //提交任务无返回值
2、public Future<?> submit() //任务执行完成后有返回值参数解释
-
1、corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程 -
2、maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize; -
3、keepAliveTime
线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime; -
4、unit
keepAliveTime的单位; -
5、workQueue
用来保存等待被执行的任务的阻塞队列,任务数少于核心线程数时不会保存。且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
1)ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2)LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3)SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4)priorityBlockingQuene:具有优先级的无界阻塞队列; -
6、threadFactory
它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。 -
7、handler线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:1、AbortPolicy:直接抛出异常,默认策略;2、CallerRunsPolicy:用调用者所在的线程来执行任务;3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;4、DiscardPolicy:直接丢弃任务;
任务执行流程
3、线程池存在5种状态
RUNNING = 1 << COUNT_BITS; //高3位为111
SHUTDOWN = 0 << COUNT_BITS; //高3位为000
STOP = 1 << COUNT_BITS; //高3位为001
TIDYING = 2 << COUNT_BITS; //高3位为010
TERMINATED = 3 << COUNT_BITS; //高3位为011
1、RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
2、 SHUTDOWN
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4、非核心线程在什么时候被回收?
如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,就会被回收。
那么我现在带入一个简单的场景,为了简单直观,我们把线程池相关的参数调整一下:
ExecutorService executorService = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),
new DefaultThreadFactory(“test”),
new ThreadPoolExecutor.DiscardPolicy());
那么问题来了:
这个线程最多能容纳的任务是不是 5 个?
假设任务需要执行 1 秒钟,那么我直接循环里面提交 5 个任务到线程池,肯定是在 1 秒钟之内提交完成,那么当前线程池的活跃线程是不是就是 3 个?
如果接下来的 30 秒,没有任务提交过来。那么 30 秒之后,当前线程池的活跃线程是不是就是 2 个?
上面这三个问题的答案都是肯定的。
接下来的问题是这样的:
如果当前线程池的活跃线程是 3 个(2 个核心线程+ 1 个非核心线程),但是它们各自的任务都执行完成了,都处于 waiting 状态。然后我每隔 3 秒往线程池里面扔一个耗时 1 秒的任务。那么 30 秒之后,活跃线程数是多少?
先说答案:还是 3 个。
从我个人正常的思维,是这样的:核心线程是空闲的,每隔 3 秒扔一个耗时 1 秒的任务过来,所以仅需要一个核心线程就完全处理的过来。
那么,30 秒内,超过核心线程的那一个线程一直处于等待状态,所以 30 秒之后,就被回收了。 但是上面仅仅是我的主观认为,而实际情况呢?
30 秒之后,超过核心线程的线程并不会被回收,活跃线程还是 3 个。
**因为实际在把任务分给线程的时候,是轮询分配的,也就是说非核心线程在3个任务的时间内一定会被分配一次,所以它并没有一直闲着,所以没回收它 **
测试代码如下:
package ThreadTest.ThreadPool;
import java.util.concurrent.*;
public class ThreadPoolServiceTest {
public static void main(String[] args) {
ThreadPoolServiceTest poolServiceTest = new ThreadPoolServiceTest();
//一开始直接塞5个认为,队列满了为2,且线程有2个核心线程,1个非核心线程
for (int i = 0; i < 5; i++) {
poolServiceTest.fun();
}
//3秒之后,全部执行完了
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
//每隔3秒推一个任务,应该只有一个线程在调用,但发现这里是轮询的,而不是偏向核心线程
//假设是偏向核心线程的,那非核心线程在10秒后会被回收,但实际上没有
System.out.println("3秒后3个线程平等的轮询调用");
for (int i = 0; i < 10; i++) {
try {
poolServiceTest.fun();
Thread.sleep(3000);
} catch (InterruptedException e) {
}
}
//停止调用10秒之后,非核心线程被回收
try {
poolServiceTest.fun();
Thread.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println("停止推任务10秒之后,非核心线程被回收,重新开始推任务,只剩下核心线程在轮询");
//重新开始推任务,只剩下非核心线程在轮询,
//谁先被回收,谁才是非核心线程,而不是谁比较慢出生谁是非核心线程
for (int i = 0; i < 10; i++) {
try {
poolServiceTest.fun();
Thread.sleep(3000);
} catch (InterruptedException e) {
}
}
}
public static ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,3,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public void fun(){
executorService.execute(()->{
System.out.println( executorService.getQueue().size());
System.out.println(Thread.currentThread().getName()+"调用:");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println( executorService.getQueue().size());
});
}
}
打印的结果:
后期线程轮询的,没有偏袒核心线程,
最后回收哪个线程是随机的,看谁先闲置10秒
2
2
pool-1-thread-2调用:
2
pool-1-thread-3调用:
pool-1-thread-1调用:
2
2
2
1
pool-1-thread-1调用:
0
pool-1-thread-2调用:
0
0
3秒后3个线程平等的轮询调用
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-2调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-2调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-2调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
停止推任务10秒之后,非核心线程被回收,重新开始推任务,只剩下核心线程在轮询
0
pool-1-thread-1调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0
0
pool-1-thread-3调用:
0
0
pool-1-thread-1调用:
0