初探线程池【手写线程池!阿里弃用之辨析】

什么是线程池

        线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。

为什么要使用线程池

        因为频繁的开启线程或者停止线程,线程需要从新被 cpu 从就绪到运行状态调度,需要发生
cpu 的上下文切换,效率非常低。

当线程从run中sleep后,会到达阻塞状态,这时候要想再回到run状态,得先就绪再到run状态,成本极高;

        分析:如果这里有100个线程,则会新建100个newThread,100次start,100次的就绪,100次的CPU调度,才能被运行,必然也有100次的销毁,整个的成本非常高!
所以我们提前创建好100个线程,都在运行状态,只要有请求过来了,直接再run方法中进行执行就行了!而不是又经过创建->就绪->运行 这个耗时的过程!
       

哪里使用到线程池

        实际开发项目中 禁止自己 new 线程。必须使用线程池来维护和创建线程。
在这里插入图片描述
实际企业开发中,如果像这样去new线程,那一定会被开除!!

如果别人找到了这个bug,比如你这个接口有每次创建线程的bug,那么别人可以通过这个bug给你服务器攻击!造成CPU飙高,线程池就可以很好的管理,管理线程创建的数量,而不是无限制的创建!

线程池有哪些作用

核心点:复用机制 提前创建好固定的线程一直在运行状态 实现复用 限制线程创建数量。
1.降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
2.提高响应速度:任务到达时,无需等待线程创建即可立即执行。
3.提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
4.提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

总结:统一维护管理

线程池的创建方式

Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();可定长度 限制最大线程数
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
底层都是基于 ThreadPoolExecutor 构造函数封装

前面四个基本上实际生产不会去用,阿里巴巴不推荐,因为底层都是由ThreadPoolExecutor 构造封装的,而构造函数中是一个无界的队列(后面解释)

Executors.newCachedThreadPool():

下面是它的源码部分 核心线程数传的0,最大是无限,没有界限,如果用这个无限的去创建,而不是复用机制,故不去用!这个api实际开发不用! 可以改成可定长度线程池,这样即使for循环有10个线程创建,但是会被传入的nThread所限制。 # 线程池底层是如何实现复用的 本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。 1. 提前创建固定大小的线程一直保持在正在运行状态;(可能会非常消耗 cpu 的资源) 2. 当需要线程执行任务,将该任务提交缓存在并发队列中;如果缓存队列满了,则会执行 拒绝策略; 3. 正在运行的线程从并发队列中获取任务执行从而实现多线程复用问题;

在这里插入图片描述
线程的核心点:复用机制------

  1. 提前创建好固定的线程一直在运行状态----死循环实现
  2. 提交的线程任务缓存到一个并发队列集合中,交给我们正在运行的线程执行
  3. 正在运行的线程就从队列中获取该任务执行

如何保证线程一直在运行,而不会停掉被销毁呢?———加一个while死循环即可

手写线程池

思路:
       首先线程池得定义一系列线程,并且构造的时候让这些线程都处于一个运行状态,用workThreads代替,构造的时候可以给定限制的工作线程数量以及任务集合中的size上限,运行状态通过改写每一个工作线程的run方法即可,利用死循环让其处于一直运行的状态!具体每一个工作线程要做的任务是什么,就通过拿取runnableDeque这个队列中的每一个任务即可,poll方法拿取后便删除该任务。
       如果想控制线程池使得结束,可以通过控制工作线程中while循环的条件,设置一个isRun的Boolean变量,并且当任务队列中没有任务了之后线程池才停止工作。

public class MyExecutors {
    private List<WorkThread> workThreads;
    // 缓存我们线程任务
    private BlockingDeque<Runnable> runnableDeque;
    private boolean isRun = true;

    /**
     * 最大线程数
     *
     * @param maxThreadCount
     */
    public MyExecutors(int maxThreadCount, int dequeSize) {
        //1.限制队列容量缓存
        runnableDeque = new LinkedBlockingDeque<Runnable>(dequeSize);
        //2.提前创建好固定的线程一直在运行状态----死循环实现
        workThreads = new ArrayList<WorkThread>(maxThreadCount);
        for (int i = 0; i < maxThreadCount; i++) {
            new WorkThread().start();
        }

    }

    class WorkThread extends Thread {
        @Override
        public void run() {
            while (isRun||runnableDeque.size()>0) {
                Runnable runnable = runnableDeque.poll();
                if (runnable != null) {
                    runnable.run();
                }
            }
        }
    }

    public boolean execute(Runnable command) {
        return runnableDeque.offer(command);
    }

    public static void main(String[] args) {
        MyExecutors myExecutors = new MyExecutors(2, 20);
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            myExecutors.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "," + finalI);
                }
            });
        }
        myExecutors.isRun = false;
    }
}

ThreadPoolExecutor核心参数

corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出 corePoolSize 后创建的线程的存活时间。
unit:keepAliveTime 的时间单位。
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。

线程池创建的线程会一直在运行状态吗?

不会
例如:配置核心线程数 corePoolSize 为 2 、最大线程数 maximumPoolSize 为 5
我们可以通过配置超出 corePoolSize 核心线程数后创建的线程的存活时间例如为 60s,在 60s 内非核心线程一直没有任务执行,则会停止该线程。
总结:核心线程数是一直在运行的,但最大线程数是不一定的,看具体任务,如果任务的情况下补给到最大线程范围内更多的线程进来工作,最大线程减去核心线程的这部分线程在工作完后再销毁。
目的:为了节约服务器资源

为什么阿里巴巴不建议使用 Executors

        因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生内存溢出,会导致我们最大线程数会失效。

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至1OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
————————————————
版权声明:本文为CSDN博主「_夜渐凉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41888813/article/details/90769126

线程池底层 ThreadPoolExecutor 底层实现原理

1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程数大于等于核心线程数,且任务队列已满
3.1 若线程数小于最大线程数,创建线程
3.2 若线程数等于最大线程数,抛出异常,拒绝任务
看例子:
分析:首先创建了一个线程池,有2个核心线程,4个最大线程,并且blockingQueue的容量为5(用于缓存任务队列),然后我们再for中提交了10个任务进行执行,第一个和第二个任务直接被2个核心线程来执行,3、4、5、6、7就被缓存到容量为5的blockingQueue队列中了,然后遍历到第八个新任务的时候,当前的线程数已经大于了核心线程数,并且任务队列已经装满了,那就再创建(最大线程数-核心线程数)个线程,这里也就是2个新的工作线程被创建去执行第8、第9个任务,当遍历到第10个任务的时候,就已经没有工作线程来执行了,并且任务队列之前也装满了,从而拒绝任务!

public class Test07 {
    public static void main(String[] args) {
        //1.提交的线程任务数<核心线程数  (核心线程数任务复用)
        //2.提交的线程任务数>核心线程数 且我们队列容量没有满 将该任务缓存到我们队列中
        // 循环3 4 5 6 7 缓存到我们队列中
        //3.提交的线程任务数>核心线程数 且我们队列容量满了
        //8,9,10
        // 最多在额外创建两个线程 4-2 2个线程
        // 2个线程 8 ,9
        // 10个任务----拒绝
        ExecutorService executorService = MyThreadPoolExecutor.newFixedThreadPool(2, 4,5);
        for (int i = 1; i <= 10; i++) {
            final int finalI = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "," + finalI);
                }
            });
        }
        // 实际上最多执行多少个任务 核心线程数+缓存队列的容量+最大线程数-核心线程数
    }
}

自己重写的一个ExecutorService,限制了传入的核心线程数、最大线程数、以及任务队列的容量

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), (RejectedExecutionHandler) new ThreadPoolExecutor.DiscardPolicy ());
    }
}

线程池队列满了,任务会丢失吗

如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。
可以自定义异拒绝异常,将该任务缓存到 redis、本地文件、mysql 中后期项目启动实现补偿。
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务

线程池拒绝策略类型有哪些呢

1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5.实现 RejectedExecutionHandler 接口,可自定义处理器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值