这里写目录标题
1. 什么是线程池
线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。
2. 为什么要使用线程池
因为频繁的开启线程或者停止线程,线程需要从新被 cpu 从就绪到运行状态调度,需要发生 cpu 的上下文切换,效率非常低。
3. 哪些地方会使用到线程池
实际开发项目中 禁止自己 new 线程。必须使用线程池来维护和创建线程。
项目使用多线程异步发送短信—-多线程
4. 线程池有哪些作用
1.降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
2.提高响应速度:任务到达时,无需等待线程创建即可立即执行。
3.提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因
为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分
配、调优和监控。
4.提供更多更强大就允许任务延期执行或定期执行。
5. 线程池的创建方式
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();可定长度 限制最大线程数
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
真实底层都是基于 ThreadPoolExecutor 构造函数封装 线程池
6. 线程池底层是如何实现复用的
核心复用机制:最多只会创建2个线程 提交10个线程任务 涉及到缓存
- 定一个容器(LinkedBlockingQueue)缓存提交的线程任务
- 提前创建好固定数量的线程一直在运行状态(5个线程)
- 无界(无限存储元素) 有界(有限制容量存储元素)
- 提交线程任务会存放在LinkedBlockingQueue中缓存起来
- 一直在运行的线程就会从LinkedBlockingQueue取出线程任务执行。
- 如果提交线程任务到LinkedBlockingQueue中存放,如果阻塞队列满了
如何处理—走拒绝策略。
线程池缺陷:线程一直在运行状态---可能会消耗到cpu的资源
线程池底层实现原理?
本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。
- 提前创建固定大小的线程一直保持在正在运行状态;(可能会非常消耗 cpu 的资源)
- 当需要线程执行任务,将该任务提交缓存在并发队列中;如果缓存队列满了,则会执行 拒绝策略;
- 正在运行的线程从并发队列中获取任务执行从而实现多线程复用问题;
线程池核心点:复用机制
- 提前创建好固定的线程一直在运行状态—-死循环实现
- 提交的线程任务缓存到一个并发队列集合中,交给我们正在运行的线程执行
- 正在运行的线程就从队列中获取该任务执行
简单模拟手写 Java 线程池:
public class DemoExecutor {
private LinkedBlockingDeque<Runnable> linkedBlockingDeque;
private static volatile boolean isRun = true;
/**
* dequeSize队列容量大小
* threadCount 初始化一直正在运行的线程数
*
* @param dequeSize
* @param threadCount
*/
public DemoExecutor(int dequeSize, int threadCount) {
linkedBlockingDeque = new LinkedBlockingDeque<Runnable>(dequeSize);
for (int i = 0; i < threadCount; i++) {
TaskThread taskThread = new TaskThread();
taskThread.start();
}
}
public void execute(Runnable runnable) {
linkedBlockingDeque.offer(runnable);
}
class TaskThread extends Thread {
@Override
public void run() {
while (isRun || linkedBlockingDeque.size() > 0) {
Runnable runnable = linkedBlockingDeque.poll();
if (runnable != null) {
runnable.run();
}
}
}
}
public static void main(String[] args) {
DemoExecutor demoExecutor = new DemoExecutor(20, 2);
for (int i = 0; i < 10; i++) {
int finalI = i;
demoExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ",i:" + finalI);
});
}
demoExecutor.isRun = false;
}
}
7. ThreadPoolExecutor核心参数
corePoolSize:核心线程数量 一直正在保持运行的线程 2
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。满足最大线程数>=核心线程数
核心线程数量是为2 一直在运行状态 最大线程数是4 是当我们队列容量满了 就触发创建线程 最大线程数(4)-核心线程数量(2) 额外在创建2个线程来帮忙
keepAliveTime:超出corePoolSize后创建的线程的存活时间。 60s
unit:keepAliveTime的时间单位。
workQueue:任务队列,用于保存待执行的任务。—缓存提交的任务
threadFactory:线程池内部创建线程所用的工厂。–线程池内部工厂 自定义线程池
handler:任务无法执行时的处理器。—当我们队列满了,走其他处理策略
核心原理:
提交任务的时候比较核心线程数,如果当前任务数量小于核心线程数的情况下,则直接复用线程执行。
如果任务量大于核心线程数,则缓存到队列中。
如果缓存队列满了,且任务数小于最大线程数的情况下,则创建线程执行。
如果队列且最大线程数都满的情况下,则走拒绝策略。
注意:最大线程数,在一定时间没有执行任务 则销毁避免浪费cpu内存。
专业术语。
1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程数大于等于核心线程数,且任务队列已满
3.1若线程数小于最大线程数,创建线程
3.2若线程数等于最大线程数,抛出异常,拒绝任务
8. 如何自定义线程池
public class MyThreadPoolExecutor {
public static ExecutorService newFixedThreadPool(int corePoolSize, int maximumPoolSize, int blockingQueue) {
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(blockingQueue), new DemoExecutionHandler());
}
}
9. 为什么阿里巴巴java开发手册中不推荐使用jdk自带线程池
因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor 构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生 内存溢出,会导致我们最大线程数会失效。
10. 线程池队列满了拒绝策略
如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。
可以自定义异拒绝异常
rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:
1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5.实现RejectedExecutionHandler接口,可自定义处理器
队列满的话 可以将任务记录到本地磁盘或者网络中保存,后期可以直接使用人工补偿的形式。
public static ExecutorService newFixedThreadPool(int corePoolSize, int maximumPoolSize) {
linkedBlockingQueuel = new LinkedBlockingQueue<Runnable>(2);
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
0L, TimeUnit.MILLISECONDS,
linkedBlockingQueuel, new MyRejectedExecutionHandler());
}
11. 核心线程数?一直在运行状态如何避免cpu飙高问题
底层采用 runnables.poll(3, TimeUnit.SECONDS);
线程池中 核心线程 在运行中从队列中取出任务 如果队列中是为空的话,则当前
线程会阻塞,主动的释放cpu执行权。
如果有另外的线程提交向线程池中提交新的任务,则当前(核心线程会被)
主动唤醒起来 ,从队列中取出该任务执行。
12. 自定义线程池名称
自定义线程池的名称(ThreadPoolExecutor)
目的:有时候为了快速定位出现错误的位置,在采用线程池时我们需要自定义线程池的名称。
创建ThreadFactory(ThreadPoolExecutor默认采用的是DefaultThreadFactory,可以参照代码)
public class DemoNamedThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DemoNamedThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "demo" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
13. 线程池参数如何配置
- corePoolSize(核心线程数)
- queueCapacity(任务队列容量)
- maxPoolSize(最大线程数)
- keepAliveTime(线程空闲时间)
- allowCoreThreadTimeout(允许核心线程超时)
- rejectedExecutionHandler(任务拒绝处理器)
13.1 CPU密集型与IO密集型区别
核心线程数配置多少比较合理呢?
IO密集型—linux内核 用户态到内核态切换过程
Cpu密集型:当前线程做大量的程序计算;
1.CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
2.在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
3.CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
4. cpu 密集型(CPU-bound)线程池设计 最佳线程数=cpu核数或者cpu核数±1
核心数—–====cpu的核数16核
IO密集型:比如 当前线程读文件、写文件、传输文件、网络请求属于密集型;
IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。比如接收一个前端请求–解析参数–查询数据库–返回给前端这样的,那么就是IO密集型的,例如web应用。
I/O密集型(I/O-bound)线程池设计
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程
假如一个程序平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为16,那么最佳的线程数应该是? 核心线程数==64
Int j =1;
Int z=2;
// rpc请求 1.5s
根据上面这个公式估算得到最佳的线程数:((0.5+1.5)/0.5)*16=64。
代码cpu密集型 阻塞时间比较少
13.2 SpringBoot项目中如何整合线程池
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class DemoThreadPoolConfig {
/**
* 线程核心数
*/
@Value("${demo.thread.corePoolSize}")
private int corePoolSize;
/**
* 线程最大数
*/
@Value("${demo.thread.maxPoolSize}")
private int maxPoolSize;
/**
* 任务容量
*/
@Value("${demo.thread.queueCapacity}")
private int queueCapacity;
/**
* 允许空闲时间,默认60
*/
@Value("${demo.thread.keepAlive}")
private int keepAlive;
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
threadPoolTaskExecutor.setKeepAliveSeconds(keepAlive);
threadPoolTaskExecutor.setThreadNamePrefix("demoThread-");
//设置拒绝策略 当线程数达到最大时,如何处理新任务
//CallerRunsPolicy 不由线程池中线程执行,由调用者所在线程执行
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
return threadPoolTaskExecutor;
}
}
14. 线程池五种状态
线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated
线程池内部的5种状态
1.RUNNING
状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
2.SHUTDOWN
状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3.stop
状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4.TIDYING
状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5.TERMINATED
状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131198.html