线程池七大参数
① corePoolSize:线程池的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务
② maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize
③ keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出
⑤ unit:这个用来指定 keepAliveTime 的单位,比如秒:TimeUnit.SECONDS
⑥ workQueue:一个阻塞队列,提交的任务将会被放到这个队列里
⑦ threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3
⑧ handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用
package lesson1;
import java.util.concurrent.*;
public class ThreadPoolTest {
public static void main(String[] args) {
// 双向队列
// 创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 创建一个线程大小的线程池,如果有多个任务,就按顺序执行
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
// 定时任务
ExecutorService executorService2 = Executors.newScheduledThreadPool(1);
// 根据线程数量来决定线程池大小,线程执行完后返回线程池,供下一次任务使用
ExecutorService executorService3 = Executors.newCachedThreadPool();
ExecutorService excutor = new ThreadPoolExecutor(
3,// 创建核心线程数:类似于正式员工的数量
5,// 最大线程数:类似于正式员工的数量+临时员工的数量
60,// 和下面的参数结合起来,表示临时创建的线程如果没有任务时,最长的存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),// 阻塞队列:保存任务的队列,类似于物流存放包裹的仓库
// 线程创建的工厂类:创建临时线程的要求,类似于找临时工的要求
new ThreadFactory() {
public Thread newThread(Runnable r) {
return null;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 如果线程数达到最大,拒绝新线程加入的策略
// 拒绝策略
// CallerRunsPolicy 谁让执行任务,就拒绝,让它去执行任务
// AbortPolicy 拒绝执行任务,抛出异常 RejectedExecutionException
// DiscardPolicy 丢弃时间最新的任务
// DiscardOldestPolicy 丢弃时间最久的任务
);
}
}
通常是通过这五种方式来创建线程池,其中前四种创建的方式都是依赖 ThreadPoolExecutor 来完成
1.创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
描述:给定一个指定的线程数量,线程池中最大线程数量不能超过指定的值,如果当前要执行的任务数量少于线程池中的线程数量,那么其他多余的线程不会被销毁,它们等待其他任务的到来,如果任务数量超过可用线程的数量,那么多出来的任务进入 LinkedBlockingQueue 阻塞等待队列,执行完任务的线程会去执行其他的任务
2.创建单个线程大小的线程池
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
// 源码
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
描述:该线程池中只有一个线程来执行任务,如果执行过程中出现异常导致线程终止,那么会启动一个新的线程继续执行该任务,这也是和 newFixedTreadPool(1) 不同的地方,如果任务数量大于1,那么就等待,执行任务的顺序就是提交任务的顺序
3.创建定时任务
ExecutorService executorService2 = Executors.newScheduledThreadPool(1);
描述:任务会按照指定时间来执行
4.根据自身需求来创建线程池
ExecutorService executorService3 = Executors.newCachedThreadPool();
描述:没有线程数量限制,不能超过操作系统能够创建最大数量的线程,如果当前线程池中可用线程数量小于任务数量,就会创建新的线程来执行任务,每次线程执行完任务会回到线程池重新等待执行其他任务,如果等待时间超过 60 秒,那么线程就会被销毁,这样程序的性能就会提高不少
四种拒绝策略:
AbortPolicy: 当任务添加到线程池中被拒绝时,它丢弃任务并且抛出 RejectedExecutionException 异常 (默认的拒绝策略)
CallerRunsPolicy:当任务添加到线程池中被拒绝时,由提交任务的线程来执行该任务
DiscardOldestPolicy:当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最久的未处理任务,然后将被拒绝的任务添加到等待队列中
DiscardPolicy:当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务但是不抛出异常
线程池中的核心线程会被销毁吗?
答案:会,在一般情况下核心线程是不会被销毁的,如果没有任务执行的核心线程也会进行等待,会销毁目前空闲出的临时线程,因为线程的创建和销毁是有一定的成本损耗的,如果频繁的创建和销毁那么必然会带来大开销,核心线程的目的就是起到中间过渡缓存的目的,让大部分情况下的任务都能得到执行处理,当一些特殊情况下例如任务数量特别多的时候,才会创建临时线程来辅助执行任务
以下为会销毁核心线程的几种情况
(1)allowCoreThreadTimeOut参数, 这个如果为true 则会给核心线程数设置超时等待时间 如果超过时间了 就会销毁 默认是false 这个参数不是这次的重点 因为默认是关闭的 一般不会特意开启
(2)如果在 Runnable 中的 run 方法中进行业务处理的时候,发生了异常,但是没有捕获,那么也会通过 remove() 方法销毁当前的线程,并且重新创建一个线程,在 newSingleThreadExecutor 就是执行了这样的逻辑
(3)当使用 newSingleThreadExecutor 也就是单线程池时,如果在线程执行过程中发生异常导致线程中止,那该核心线程就会销毁,并且重新创建一个线程继续执行
线程池的作用?为什么要使用线程池?线程池的好处?
使用线程池的好处:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
提高线程的可管理性:对创建的线程池进行统一的管理。
实现任务线程队列缓存策略和拒绝机制。
实现某些特殊的功能,如定时执行、周期执行
线程池的工作流程/工作原理
首先提交任务,当任务进来的时候,先判断是否有空闲的核心线程,有的话让该核心线程执行任务,没有的判断核心线程数量是否已经达到核心线程数量的规定上限(也就是 corePoolSize),没有的话就创建一个新的核心线程来执行任务,达到上限则判断等待队列(workQueue)是否满了,没有满的话就加入等待队列,满了的话就判断当前线程数量是否达到了线程池可容纳的最大线程数量(也就是 maximumPoolSize),没有达到上限的话就通过工厂(threadFactory)创建一个临时线程来执行任务,达到上限则执行拒绝策略(handler),其中当临时线程处于空闲状态的时间达到规定设置的时间大小(也就是 keepAliveTime)则销毁该临时线程