【HBZ分享】java的线程池从创建 到 运行的原理

本文深入解析了Java线程池的核心参数,包括corePoolSize、maximumPoolSize、workQueue和keepAliveTime,并详细阐述了线程池的工作流程。在30个任务的场景下,演示了线程池如何处理任务。同时,文章指出Executors的四个实现类可能引发内存溢出问题,不推荐直接使用。最后,给出了一个简单的线程池实现示例。
摘要由CSDN通过智能技术生成

一. 线程池参数含义

  1. corePoolSize: 核心线程数, 即线程池会提前把核心线程给创建出来,比如corePoolSize = 5, 则会创建5个线程,这5个线程一旦被创建,是不会被回收的,会不断的获取队列中的内容,如果有内容则开始处理
  2. maximumPoolSize:最大线程池数, 即整个线程池最多能开启多少个线程,触发条件是,当核心线程池数被占满了,并且缓冲队列也满了,此时会格外创建线程来处理任务,比如maximumPoolSize = 10, corePoolSize = 5, 那么此时会再创建10-5 = 5个线程来处理任务,如果maximumPoolSize也达到了上限。则触发淘汰策略。
  3. workQueue:缓冲队列,线程池获取任务都是从缓冲队列来的,即使第一次启动线程池,任务也是进入缓冲队列,然后核心线程通过while死循环去队列拿值,类型是LinkedBlockingDeque(queueCount),其中queueCount就是指定队列大小。
  4. keepAliveTime:线程存活时间,这个存活时间指的是,超出核心线程数的其他线程存活时间,默认1分钟。切记,存活时间到了并不会回收核心线程池,只会回收超过核心线程池的部分,比如corePoolSize = 5, 但现在开启了8条线程,多出来3条,那么如果在keepAliveTime时间范围内,这3条线程一直处于空闲状态,没有干活,则会回收这3条线程,只保留核心线程池的5条线程。注意是1分钟内这3条持续没有任务的情况,如果有任务则会刷新keepAliveTime时间,重新置回1分钟。
  5. unit:时间单位, 存活时间是时,分,还是秒, 靠这个配置
  6. handler: 淘汰策略,类型:RejectedExecutionHandler, 默认是超过maximumPoolSize时,后来的线程直接丢弃

二. 线程从启动 到 应用的流程

前提:corePoolSize = 5, maximumPoolSize = 10, workQueue = 8, 此时来了30个任务

  1. 启动时,回直接初始化好workQueue的8个空间的队列。
  2. 然后会创建5个空线程,每个线程都是通过死循环来保障持续运行。
  3. 这5个线程都会不断读取workQueue队列获取任务
  4. 当20个任务来的时候,通过execute方法,该方法中通过workQueue.offer(runnable) 把任务加入到缓冲队列
  5. 此时缓冲队列有值了,则之前创建好的核心线程就会读到队列值,然后调用run()方法
  6. 当5个任务到来时,核心线程数已经满了,则6-14个任务会保存在workQueue缓冲队列中。
  7. 当第15-20个任务来的时候,由于corePoolSize 和 workQueue都满了,所以会格外再开5个线程来执行任务,格外的这5个线程是maximumPoolSize - corePoolSize = 5得来的。
  8. 从第21个任务开始,corePoolSize ,maximumPoolSize , workQueue 全满了,则21-30个任务会放弃

第一步:创建队列LinkedBlockingDeque(queueCount)

首先初始化的时候,会创建一个LinkedBlockingDeque(queueCount)类型的队列,参数就是我们指定的队列长度。

第二步:创建线程

然后会创建线程,常见的线程数 = 核心线程数,配置好核心线程数后,初始化的时候,会创建对应核心线程数的线程,这些线程的run()中会有一个while的死循环,在while中不断去读取LinkedBlockingDeque(queueCount)这个队列,如果队列有值,则直接通过poll(),取出队列值,并从队列删除该元素,然后再调用该值的run()方法即可

class MyThread extends Thread{
        @Override
        public void run() {
            // 4. 死循环中不断读取队列,当为false或者队列为空时关闭
            while (startThread || blockingDeque.size() > 0){
                // 取出队列最开始的元素,并从队列中删除
                Runnable poll = blockingDeque.poll();
                if(poll != null){
                    poll.run();
                }
            }
        }
    }

第三步:向队列中插入任务

通过execute(Runnable runnable),把创建的线程插入到队列,等待死循环while中执行该线程

    public boolean execute(Runnable runnable){
        // 3. 向队列插入Runnable类型任务
        return blockingDeque.offer(runnable);
    }

第四步:如何应用线程池

当我们要使用线程池的时候,可以这样调用, 就加入到线程池了

 myExecuter.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() );
                }
            });

第五步:为什么不推荐使用Executors?

  1. Exxcutors的4个实现类是:newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor, newCachedThreadPool
  2. 这4个实现类的共同特点是都是使用无界队列
  3. 其中newCachedThreadPool, newScheduledThreadPool 这俩的默认maximumPoolSize = Integer.MAX_VALUE, 即不限制最大线程数
  4. 其中newFixedThreadPool, newSingleThreadExecutor这俩的默认workQueue = Integer.MAX_VALUE, 即不限制最大队列数
  5. 所以无论是不限制maximumPoolSize 还是 workQueue 都可能导致内存溢出,所以极不推荐
  6. LinkedBlockingQueue无参构造方法的队列长度默认就是Integer.MAX_VALUE
源码:
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

第六步:手写一个简单的线程池

/**
 * 1. 创建队列LinkedBlockingDeque
 * 2. 创建指定数量的无限循环线程
 * 3. 向队列插入Runnable类型任务
 * 4. 死循环中不断读取队列
 */
public class MyExecuter {

    /**
     * 核心线程数个队列数量
     */
    private List<MyThread> myThreadList;

    /**
     * 队列对象
     */
    private BlockingDeque<Runnable> blockingDeque;

    /**
     * 队列长度限制
     */
    private int queueCount;

    /**
     * 核心线程池数
     */
    private int coreThreadCount;

    /**
     * 最大线程池数
     */
    private int maxThreadCount;

    private boolean startThread = true;
    /**
     * 构造方法初始化
     * @param coreThreadCount   核心线程池数量
     * @param queueCount         队列数量
     * @param maxThreadCount    最大线程池数量
     */
    public MyExecuter(int coreThreadCount, int queueCount, int maxThreadCount){

        this.queueCount = queueCount;
        this.coreThreadCount = coreThreadCount;
        this.maxThreadCount = maxThreadCount;

        // 1. 初始化队列
        blockingDeque = new LinkedBlockingDeque<Runnable>(queueCount);

        // 2. 初始化线程
        myThreadList = new ArrayList<MyThread>(coreThreadCount);
        for(int i = 0; i < coreThreadCount; i++){
            new MyThread().start();
        }
    }

    class MyThread extends Thread{
        @Override
        public void run() {

            // 4. 死循环中不断读取队列,当为false或者队列为空时关闭
            while (startThread || blockingDeque.size() > 0){
                // 取出队列最开始的元素,并从队列中删除
                Runnable poll = blockingDeque.poll();
                if(poll != null){
                    poll.run();
                }

            }

        }
    }

    /**
     * 向队列中插入Runnable任务
     */
    public boolean execute(Runnable runnable){
        // 3. 向队列插入Runnable类型任务
        return blockingDeque.offer(runnable);
    }


    public static void main(String[] args) {
        MyExecuter myExecuter = new MyExecuter(2, 2, 4);

        for(int i = 0; i < 10; i++){
            final int ss = i;
            myExecuter.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "," + ss);
                }
            });
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值