一、概述
1、ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务;
2、Executors方法提供的线程服务,都是通过参数设置来实现不同的线程池机制。
3、先来了解其线程池管理的机制,有助于正确使用,避免错误使用导致严重故障。同时可以根据自己的需求实现自己的线程池
ThreadPoolExecutor 类
构造方法
public ThreadPoolExecutor(
int corePoolSize, //核心线程数量:如果当前运行的线程数量没有达到 corePoolSize,则新建一个线程,否则加入到任务队列中
int maximumPoolSize, //最大线程数:当前系统最多存在的线程数
long keepAliveTime, //最大空闲时间:线程空闲的最大时间,超出时间该线程会销毁;设置allowCodeThreadTimeOut(true/false),可控制核心线程是否销毁,默认false 表示允许核心线程即使超过最大线程时间也不会销毁
TimeUnit unit, //时间单位: 线程空闲的最大时间的单位
BlockingQueue<Runnable> workQueue, //任务队列: 核心线程数量满了之后,提交的任务加入到队列中,等待核心线程数减少后再去创建线程;当任务队列已满,但没有达到最大线程数时,则新建非核心线程
ThreadFactory threadFactory, //线程工厂: 自定义线程的创建
RejectedExecutionHandler handler //饱和处理机制:当任务队列已满且达到最大线程数时,采取的措施
)
线程池原理
线程池底层使用**堵塞式队列 BlockingQueue **。
队列遵从:先进先出,后进后出原则。
阻塞队列(BlockingQueue)和非阻塞队列(ConcurrentLinkedQueue )区别:
无界和有界队列:
ConcurrentLinkedQueue 是无界队列,不用设置长度,可以随便存放值(其实是jdk伪造的,最大长度是Integer的最大值)
BlockingQueue 是有界队列,需要设置长度。
注意:如果BlockingQueue 不设置等待时间就是非阻塞队列
存入:
非阻塞队列:如果存放超出了队列总数,添加不进去,就会丢失。
阻塞队列:如果存放超出了队列总数,进行等待,直到有队列出列,或者超时设置的等待时间)
获取:
非阻塞队列:如果为空时,返回空。
阻塞队列:如果为空时,进行等待,直到有新的队列入列,或者超过设置的等待时间
创建线程池的构造方法
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
- ThreadPoolExecutor
- 参数说明
- 核心线程大小(corePoolSize)
- 最大线程大小(maximumPoolSize)
- 终止时间(keepAliveTime)
- Unit 超时时间
- workQueue 线程容器
自定义线程池
1、编写任务类
public class MyTask implements Runnable{
//任务id
private int id;
public MyTask(int id){
this.id=id;
}
@Override
public void run() {
String name=Thread.currentThread().getName();
System.out.println("线程:"+name+"-->即将执行任务"+id);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+"执行完成"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
2、编写线程类,用于执行任务
public class MyThread extends Thread{
private List<Runnable> tasks;
public MyThread(String name, List<Runnable> tasks){
super(name);
this.tasks=tasks;
}
@Override
public void run() {
while (tasks.size() > 0){
Runnable r= tasks.remove(0);
r.run();
}
}
}
3、编写线程池类,用于管理线程的执行
public class MyThreadPool {
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
/**
* 当前线程数
*/
private int num;
/**
* 核心线程数
*/
private int corePoolSize;
/**
* 最大线程数
*/
private int maxSize;
/**
* 任务队列数
*/
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
/**
* 提交任务
*/
public void submit(Runnable r){
if (tasks.size()>=workSize && tasks.size() > maxSize){
System.out.println("任务:"+r+"被丢弃了");
}else{
tasks.add(r);
execTask(r);
}
}
public void execTask(Runnable r){
if (corePoolSize > num){
new MyThread("核心线程:"+num,tasks).start();
num++;
}else if(num < maxSize){
new MyThread("非核心线程:"+num,tasks).start();
num++;
}else{
System.out.println("任务:"+r+"被缓存了");
}
}
}
4、测试
public class Demo {
public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(2, 4, 20);
for (int i =0;i< 300;i++){
MyTask myTask = new MyTask(i);
myThreadPool.submit(myTask);
}
}
}
文末
1、用ThreadPoolExecutor自定义线程池,看线程是的用途,如果任务量不大,可以用无界队列,如果任务量非常大,要用有界队列,防止OOM
2、如果任务量很大,还要求每个任务都处理成功,要对提交的任务进行阻塞提交,重写拒绝机制,改为阻塞提交。保证不抛弃一个任务
3、最大线程数一般设为2N+1最好,N是CPU核数
4、核心线程数,看应用,如果是任务,一天跑一次,设置为0,合适,因为跑完就停掉了,如果是常用线程池,看任务量,是保留一个核心还是几个核心线程数
5、如果要获取任务执行结果,用CompletionService,但是注意,获取任务的结果的要重新开一个线程获取,如果在主线程获取,就要等任务都提交后才获取,就会阻塞大量任务结果,队列过大OOM,所以最好异步开个线程获取结果
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓