线程池中的线程是如何运行的

1.前言

线程池创建一定数量的线程,并加到池中等待工作,这种池化技术可以复用我们的线程,并且还能根据不同的策略去执行任务,这篇文章主要探讨下线程池里的线程如何运行,以及如何复用线程的

2.任务的执行

先看下如何将我们的任务提交给线程池

    public static void main(String[] args){
        //通过工厂类创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0;i < 5;i++){
            int finalI = i;
            //提交任务
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName()+"完成任务" + finalI );
            });
        }
    }

运行结果如下

6e94acd01a7e437db1a4b5e8b9d8341a.png  

 通过调用线程池对象的submit方法就可以把我们的任务提交上去,并且从结果可以看出,由线程池的两个线程完成我们的任务,这样就实现了线程的复用

线程池的submit方法是有返回值的,其返回值是Future类,如果我们submit的参数里传入的任务是有返回值的,那么当任务执行完毕,返回的Future类调用get方法可以得到我们的返回值,这里不作赘述

3.任务的封装

下面我们来看下submit之后,我们的任务经历了哪些过程(下面以ThreadPoolExecutor作介绍)

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        //将任务封装
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

可以看到第一步是将我们的任务进行封装,这里我们先不关心封装的类是什么,因为在execute方法中是把我们的ftask当作Runnable接口来使用的,下面看下execute方法

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        /*
         * 这里有一段java官方写的英文注释,描述了后面代码执行的逻辑
         */

        int c = ctl.get();
        //如果判断出小于核心线程,则尝试新增一个核心线程来执行任务
        if (workerCountOf(c) < corePoolSize) {
            //创建核心线程去执行 第二个参数代表是否为核心线程
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //判断是否要拒绝还是创建非核心线程来执行
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

可以看到在添加任务前,会先判断当前线程池核心线程数量是否达到最大值,如果未达到最大值,则创建一个核心线程去执行。

如果达到最大值。首先会去查看线程池运行的状态(isRunning查看线程池是否是运行状态),然后尝试加入等待队列。如果成功加入等待队列,那么会再次判断线程运行的状态,如果发现此时线程池关闭,则会移出刚才添加的任务,并执行拒绝策略。如果线程池处于运行状态,但是发现没有工作线程(workerCountOf返回工作线程数),则创建非核心线程去执行

最后如果无法加入等待队列,则尝试创建非核心线程去执行,如果失败则执行拒绝策略

经过这一系列的判断后我们的任务才能开始被执行,下面来看下addWorker方法

    private boolean addWorker(Runnable firstTask, boolean core) {
        
        //这里有一段判断运行状态的代码...

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //封装worker类
            w = new Worker(firstTask);
            //实际执行的线程
            final Thread t = w.thread;
            if (t != null) {
                //这里会有个加锁的操作
                try {
                    //有一段判断状态的代码
                    //如果判断都通过则我们创建出来的worker会被加入我们的workers集合中
                } finally {
                    //释放锁
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

(上面我忽略的比较多的代码,每段代码大意已经用注释形式给出)

可以发现我们传入的任务被传入到一个worker对象,worker类是线程池的一个内部类,是对线程的一个封装,worker实现了Runnable接口,所以其本身就有run方法,并且内部维护了一个Thread类。

可以看到最后调用了start方法来启动我们的线程,到这里我们的任务就封装好,可以看到中间经过了层层判断,只能说java的设计师太 ‘细’  了

4.Worker

前面说过worker类实现了Runnable接口,所以其实worker本身就作为一个任务来执行,而其维护的Thread调用的也是worker的run方法

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

下面来看下我们的worker类是如何运行的

        public void run() {
            runWorker(this);
        }

可以发现worker的run方法调用的是runWorker这个方法,并且传入了其本身,来看下这个方法是如何运行的

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        //省略部分代码...
        try {
            while (task != null || (task = getTask()) != null) {
                //一段判断的代码...
                try {
                    //...
                    try {
                        task.run();
                    } 
                    // ...
                } finally {
                    task = null;
                    w.completedTasks++;
                    //...
                }
            }
            //...
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

(在上面我省略了比较多的代码段,主要是判断和一些加锁的操作)

可以发现实际上执行的是一个while循环,这个循环会不断的去获取任务,然后执行,这里的getTask方法就是去任务队列中获取任务我们的线程会阻塞在这个位置,下面来看下getTask是如何去获取任务的

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            //省略部分代码...
                
            //判断是否要线程收缩/销毁非核心线程
            int wc = workerCountOf(c);
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

(上面省略了部分代码)

可以发现是通过workQueue的poll或者take方法来获取的,poll方法会传入一个等待时间,如果等待时间到没有返回,就会执行下面的timedOut = true,在下次判断就会退出这个方法,导致外层得到的是null,最后退出while循环,take方法则一直阻塞等待,具体阻塞方式由实现类实现(比如通过锁之类的)

5.总结

以上就是本人对线程池复用线程的一个理解,创建出来的线程会在一个while循环中不断的尝试获取任务队列里的任务,直到没有任务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值