提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、什么是线程池
- 二.为什么要有线程池
- 引入:
- 为什么从池子里取,比创建线程速度要快
- 什么是用户态,什么是内核态
- 最终结论:
- 三.标准库中的线程池
- 四.自己实现一个线程池
- 生产者消费者模型策略:
- 代码演示:
- 完整代码:
- 五.线程池的拒绝策略 (超级重点):
- 引入
- 标准库中拒绝策略(对于threadpoolExecutr的理解)
- 延伸的问题:
- 实际开发中线程池数目如何确定:
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是线程池
类似String字符串常量池,mysql jdbc数据库链接池
搞一个池子,创建好许多线程,当需要执行任务的时候,不需要在重新创建线程,而是直接从池子里取一个现成的线程,直接使用,用完了也不释放,而是直接放回线程池里
二.为什么要有线程池
引入:
线程诞生的目的是进程太重量了,创建进程或销毁进程,都是比较低效(内存资源的申请和释放),线程就是共享了内存资源,新的线程复用之前的资源,不必重新申请,但是如果线程创建的速率进一步频繁,此时线程创建开销仍然不能忽略此时可以采用线程池来优化速度
为什么从池子里取,比创建线程速度要快
实际上,创建线程也需要申请资源(很少),创建线程,也是要在操作系统内核中完成,涉及到用户态->内核态切换操作,也存在一定的开销
什么是用户态,什么是内核态
应用程序发起的一个创建线程的行为,本质上是pcb,是内核中的数据结构,
应用程序需要通过系统调用,进入到操作系统内核中执行,
内核完成pcb的创建,把pcb加入到调度队列,再返回给应用程序
从线程池取线程,把线程放回线程池,这是纯用户态的逻辑
从系统这里创建线程,是用户态+内核态共同完成的逻辑
最终结论:
使用线程池是纯用户态的操作,要比创建线程(经历内核态的操作)要快**
三.标准库中的线程池
ExecutorService pool= Executors.newCachedThreadPool();
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("这是任务");
}
});
0.标准库中线程池构造方法的参数
1.如果直接采取构造方法创建线程池(参数太多)写起来麻烦,为了简化构造,标准库提供了一系列工厂方法
2.此处创建线程池没有显示new,而是通过executor类的静态方法newCachedThreadpool来完成
3.工厂模式的好处:
绕开构造方法的局限(参数太多),而且构造方法有时候不能重载(返回值,参数列表都相同)
我们可以使用普通方法代替构造方法(普通方法方法名可以随便取来重载),在普通方法创建point对象,通过其他手段设置
public static Point makePointByXY(double x,double y){
Point p=new Point();
p.setR(x);
p.setA(y);
return p;
}
private void setA(double a){
}
private void setR(double r){
}
线程池的使用采取submit方法,把任务提交到线程池中即可,线程池里面有线程完成这里的任务
四.自己实现一个线程池
一个线程池可以同时提交n个任务,对应的线程池就有m个线程来负责完成这n个任务
生产者消费者模型策略:
先搞一个阻塞队列,每个被提交的任务都被放到阻塞队列当中,
搞m个线程来取队列元素,如果队列空了,m个线程自然阻塞等待,
队列不为空,每个线程都取任务,执行任务,再取下一个任务,直到队列为空
代码演示:
public MyThreadPool(int m){
//在构造方法中,创建出m个线程,负责完成工作
for (int i = 0; i <m ; i++) {
Thread t=new Thread(()->{
while(true){
try {
Runnable runnable=queue.take();
runnable.run();
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
}
创建m个线程,让线程持续的扫描队列,执行其中方法
完整代码:
package Threading;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
//演示自己创建的线程池
class MyThreadPool{
private BlockingQueue<Runnable> queue=new LinkedBlockingDeque<>();
public void submit(Runnable runnable) throws InterruptedException{
queue.put(runnable);
}
public MyThreadPool(int m){
//在构造方法中,创建出m个线程,负责完成工作
for (int i = 0; i <m ; i++) {
Thread t=new Thread(()->{
while(true){
try {
Runnable runnable=queue.take();
runnable.run();
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
}
}
public class demo28 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(10);
for (int i = 0; i <1000 ; i++) {
int taskId=i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("执行当前任务: "+taskId+"当前线程: "+Thread.currentThread().getName());
}
});
}
}
}
五.线程池的拒绝策略 (超级重点):
引入
线程池的拒绝策略,线程池的任务队列已经满了(工作线程已经忙不过来了)
当前任务数>workqueue.size()+maximumpoolsize
如果又有人往里添加新的任务,问题:
这个策略对于高并发服务器,也是非常有意义的
标准库中拒绝策略(对于threadpoolExecutr的理解)
AbortPolicy 直接丢弃任务,抛出异常,必须处理好异常,否则打断当前执行流程默认策略
CallerrunsPolicy 只用调用者所在的线程来处理任务
discradoldesrPolict 丢弃等待队列中最旧的任务,并执行当前任务
DiscardPolicy 直接丢器,啥事没有
延伸的问题:
实际开发中线程池数目如何确定:
实践是检验真理的唯一标准
针对你的程序进行性能测试,分别给线程设置成不同的数目,分别记录每种情况下,你的程序一些核心性能制表和系统负载情况,选一个最合适的