多线程 (九) 线程池的使用及实现
🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!
人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习!欢迎志同道合的朋友一起加油喔🦾🦾🦾
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿
谢谢你这么帅气美丽还给我点赞!比个心
目录
一.线程的概念
二、创建线程池的方式
三.线程池的创建(工厂模式创建)
四.线程池工作原理及参数
五. 四种拒绝策略
六.自定义一个线程池
一.线程的概念
线程池:一个容纳多个线程的容器,容器中的线程可以重复使用,省去了频繁创建和销毁线程对象的操作。
线程池作用:
- 降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度,当任务到达时,如果有线程可以直接用,不会出现系统僵死。
- 提高线程的可管理性,如果无限制的创建线程,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的核心思想:线程复用,同一个线程可以被重复使用,来处理多个任务。
池化技术 (Pool) :一种编程技巧,核心思想是资源复用,在请求量大时能优化应用性能,降低系统频繁建连的资源开销。
为什么从线程池取线程要比从系统申请效率更高呢
因为从线程池取线程是纯粹的用户态操作
从系统创建线程,涉及到内核态和用户态的切换
内核态用户态是什么?
操作系统对程序的执行权限进行分级,分别为用户态和内核态。用户态相比内核态有较低的执行权限,很多操作是不被操作系统允许的,简单来说就是用户态只能访问内存,防止程序错误影响到其他程序,而内核态则是可以操作系统的程序和普通用户程序
内核态: cpu可以访问计算机所有的软硬件资源
用户态: cpu权限受限,只能访问到自己内存中的数据,无法访问其他资源
为什么要有用户态和内核态?
系统需要限制不同的程序之间的访问能力,防止程序获取不相同程序的内存数据,或者外围设备的数据,并发送到网络,所有cpu划分出两个权限等级用户态和内核态
举个简单的例子加深理解
一位滑稽老铁去银行取钱,如果自己操作ATM机取钱效率很高,如果让工作人员去帮忙取钱,此时他可能在取钱的过程中先接杯水,再和别人唠会嗑啥的,效率会很低!
这里的滑稽老铁就是用户态,工作人员就是内核态,滑稽老铁自己取钱相当于线程池取线程,是纯用户态的操作,而交给工作人员去取钱相当于向系统申请资源创建线程,涉及到到内核态和用户态的切换
二、创建线程池的方式
线程池的创建⽅式总共包含以下 7 种(其中 6 种是通过 Executors 创建的, 1 种是通过
ThreadPoolExecutor 创建的):
1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
三.线程池的创建(工厂模式创建)
工厂模式实际上就是拿来填构造方法的坑的
例如当我们算一个坐标的时候,我们可以通过横纵坐标构造一个点,也可以通过极坐标的方式构造一个点,但是在构造的时候我们发现两种的参数是一样的,无法构成重载
于是就可以利用工厂模式构建一个工厂类,搞两个普通的方法重载去实现,问题就能得到解决
伪代码演示:
//构造方法方法冲突
class Point{
public Point(double x,double y)
}
public Point(double r,double a){
}
}
//创建一个工厂类
class PointBuilder{
public static point getPointA(double x,double y)
}
public static point getPointB(double r,double a){
}
}
四.线程池工作原理及参数
Executors类提供4个静态工厂方法:newCachedThreadPool()、newFixedThreadPool(int)、newSingleThreadExecutor和newScheduledThreadPool(int)。这些方法最终都是通过ThreadPoolExecutor类来完成的,这里强烈建议大家直接使用Executors类提供的便捷的工厂方法,能完成绝大多数的用户场景,当需要更细节地调整配置,需要先了解每一项参数的意义。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- corePoolSize(线程池基本大小)必须大于或等于0;
- maximumPoolSize(线程池最大大小)必须大于或等于1;
- maximumPoolSize必须大于或等于corePoolSize;
- keepAliveTime(线程存活保持时间)必须大于或等于0;
- workQueue(任务队列)不能为空;
- threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类
- handler(线程饱和策略)不能为空,默认策略为ThreadPoolExecutor.AbortPolicy。
五. 四种拒绝策略
CallerRunsPolicy呼叫着运行策略,通常叫做,调用者运行策略:是如果线程池的线程全部被用完的时候,会把多余的任务返回给调用者去执行;(敢于反驳,我干不了了,就把任务丢给发布任务的人去干,哈哈哈,打工人要学学,不能一味的屈服)
AbortPolicy终止策略:如果线程池线程被用完了,直接抛出异常 rejectedExecution从而终止任务;
DiscardPolicy丢弃策略:如果线程池的线程被用完了,不抛出异常,直接丢弃多余的任务;(很有脾气是吧,我干不了我不干,我也不吭气,直接仍了,哈哈哈)
DiscardOldestPolicy丢弃最老策略:如果线程池的线程被用完了,就把等待时间最久的任务丢弃掉;(这是不是工作中,把一些棘手的任务放到最后,一直到发布任务的人忘记了有这么一回事了,然后我们也可以不干了,哈哈哈哈)
六.自定义一个线程池
//自定义一个线程池
class MyThreadPool {
//阻塞队列用来存放任务
private BlockingQueue<Runnable> queue =new LinkedBlockingDeque<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//此处实现一个固定线程数的线程池
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t =new Thread(() -> {
try {
while (true) {
//取任务
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool =new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int number =i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello "+number);
}
});
}
}
}