Java并发编程系列 | 线程池原理详解

Java并发编程系列文章

欢迎大家观看我的博客,会不断的修正和更新文章,也欢迎大家一起交流

写在开头

线程池是非常常用的,在了解tomcat、hystrix的时候,发现都需要去深入的了解一下线程池才好,并且出了大量的框架有用到线程池以外,自己平常的开发也需要大量的使用线程池,那么,我们就先带着一下问题,阅读源代码,去深入的了解一下线程池吧(推荐直接看我写在源代码中的注释)。

  1. 线程池的运行流程是怎么的?
  2. keepalivetime是怎么保证当前worker数大于corepoolsize且空闲保活时间时,会减少线程数量
  3. 它的四种拒绝机制,是什么?什么时候会拒绝?
  4. 什么时候在哪里创建线程?有什么规则?
  5. shundown和shutdownnow有啥区别,是怎么让线程池终止的?
  6. 一个线程是怎么的切换执行多个任务的?

运行流程

在这里插入图片描述

首先创建一个线程池以后,那么就从执行任务的时候开始分析,就会开始下面的逻辑

  1. 如果当前工作线程小于核心线程数量,就直接尝试新增核心线程来执行command
  2. 核心线程数肯定是满了,加不进去了,尝试把它放到队列里面等待执行,并且需要执行双重检查,在第一次检查之后线程被shutdown了,就要拒绝并移除出队列,尤其是要防止最开始检查时有核心线程,然后检查后核心线程死了,结果放进队列的command得不到执行
  3. 如果队列也加入不进去了,尝试新增非核心线程来执行,再失败的话就只能采用拒绝策略了。
  • 从这里也可以看出来,当线程池有新的任务进来的时候,首先优先是创建核心线程来执行,然后核心线程满了以后就开始使用阻塞队列来存放任务,当阻塞队列也满了的话,就开始创建非核心线程来执行任务了,所以,如果使用的是new LinkedBlockingQueue<>()的话,那么该线程池就不会创建非核心线程,并且无论有多少任务过来,除了shutdown的时候都不会采用拒绝策略拒绝掉。
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))//如果当前工作线程小于核心线程数量,就直接新增核心线程来执行command
            return;
        c = ctl.get();
    }
    //走到这就说明核心线程数肯定是满了,加不进去了,尝试把它放到队列里面等待执行
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
            //如果说在线程死了,这时这个线程池已经没有工作线程了,需要添加工作线程从队列取出command来执行
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))//不能添加进队列,也不能直接新增线程执行的话,采用拒绝策略
        reject(command);
}
  • 在这里,addWorker内有一段代码的逻辑就是,新增一个工作线程worker,把这个worker加入到该线程池工作线程的集合中,并执行该线程,那么,这里就开始了任务执行的代码了

  • 就是在这里创建线程

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
  • 在worker中,包含一个线程,在初始化的时候创建,每new一个worker都会创建一个线程,并且,worker本身实现了Runnable接口,相当于把自己当做了一个任务,所以在执行t.start()的时候,启动了worker里的run方法
    Worker w = null;
        try {
        w = new Worker(firstTask);//新增一个工作线程
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);//加入到该线程池工作线程的集合中
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();//执行该worker工作线程
                workerStarted = true;
            }
        }
    }
  • worker里的run方法启动后,实际调用的就是runWorker这个方法,而在里面,除了第一次是创建worker时可能会自带task过来以外,其余就一直通过从阻塞队列里面获取任务来执行
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //如果任务为空就从队列里面获取任务,由于是使用阻塞队列的take()方法,队列为空时阻塞
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    //像一般在这个线程里面发生了未经捕获的异常,可以在里面做一些处理,
                    // 由于默认该方法没有做任何处理,可以通过继承ThreadPoolExecutor来重载该方法
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 一般代码执行到这里有三种情况,一种是该线程的任务发生了未捕获的异常,
        // 一种是任务队列为空,然后这个worker又在执行超时的策略,超时后也会跑到这里来
        // 一种是该线程池被shutdown了,那么从队列取出的任务为空,while条件不满足了
        // 就会移除这个worker
        processWorkerExit(w, completedAbruptly);
    }
}
  • 那么,我们可以看一下,getTask()方法,线程池的keepalive和线程池的shundown等,都和这里的实现相关。

  • keepalive是利用阻塞队列的poll()方法设置超时机制,超时后返回为null,再到runworker()方法里面通过processWorkerExit()将worker移除。

  • shundown则是通过在shundown()方法调用inturrupt()方法使得workQueue.take()被打断,再判断为打断状态返回null,到runworker()方法里面去。

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

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {//
                decrementWorkerCount();
                 //1.如果是shutdownNow()的话,就不用管队列是否为空,直接返回null
                 //2.如果是shutdown()且队列还有有任务,会先将队列消费完,再返回null
                 //3. 如果是从workQueue.take()那里被shutdown,就是队列早就是空的,就会for循环运行到这里返回null
                return null;
            }

            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) : //如果允许超时的话,利用阻塞队列的poll()方法设置超时机制
                    workQueue.take();//不允许超时的话,那就一直等待,直到shutdown的时候等待状态被interrupt打断
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
  • 再就是processWorkerExit()方法了,这个其实看源码方法上的英文注释就差不多了
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 判断是否是因为未捕获的异常引发的需要移除这个worker,因为像keepalive超时到这里来之前已经执行了decrementWorkerCount()方法了
        if (completedAbruptly) 
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);//从工作线程集合中移除worker
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            //如果说这个线程池没有被shutdown,且当前线程数大于最低要求的线程数
            // 那么虽然前面移除了worker,这里还是得要再增加一个来替换worker
            addWorker(null, false);
        }
    }
  • shutdown使用interruptIdleWorkers来打断线程,队列里面如果还有任务,就会将所有任务执行下去
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {//只有在线程不在执行任务的时候,才会tryLock成功
                    try {
                        t.interrupt();//就是说任务队列为空时,线程被阻塞在workQueue.take()时,才会被打断
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
  • shutdownNow使用interruptWorkers来打断线程,所以就算这个线程在执行任务时被阻塞依旧会被打断,然后直接执行processWorkerExit()方法
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();//在这里所有的worker是一定会被执行这个方法的
        } finally {
            mainLock.unlock();
        }
    }

回答上述的一些问题

  • (问题)它的四种拒绝机制,是什么?什么时候会拒绝?
    先说总共有2种情况下被拒绝:
    1.线程池被shutdown了,这时还往线程池里面添加任务,就会被直接拒绝
    2.线程池的最大线程数满了,任务队列也满了,被拒绝
    再看看它的四种拒绝策略:
    1.AbortPolicy
    这是默认的策略,执行的时候会抛出RejectedExecutionException异常
    2.CallerRunsPolicy
    正如它的英文名字,它的拒绝策略是采用当前调用线程来执行这个任务,而不会去使用线程池里的线程来执行了
    3.DiscardOldestPolicy
    它会从队列里面取出一个最老的任务,就是队列头部的数据丢弃,再重新调用threadPool.execute
    4.DiscardPolicy
    抛弃策略会抛弃当前的任务,而不会做任何其他操作
  • (问题)一个线程是怎么的切换多个任务执行的?
    工作线程每次执行完任务后,都会进行队列的等待,当加入新的任务到队列以后,就会取出来执行,然后再进行等待,再执行,循环往复。
  • (问题) 什么时候在哪里创建线程?有什么规则?
    每一个worker中都有一个thread字段,在new Worker() 的时候,就会在构造器里将这个线程初始化,并且ThreadPoolExecutor使用HashSet将所有的worker都存了一个集合出来用于管理,创建线程的需要满足的任一条件:
    1.当前HashSet中的worker数小于核心线程数
    2.核心线程数满了,队列也满了,那就开始创建非核心线程
  • (问题)keepalivetime的机制是怎么实现的?
    当当前的线程数要大于核心线程数时,一个线程执行完任务后,就会自动通过workQueue.poll() 来获取任务,而keepalive是利用阻塞队列的poll() 方法的参数设置超时机制,超时后直接返回为null,再到runworker() 方法里面通过processWorkerExit() 将worker移除。
  • (问题)shundown和shutdownnow有啥区别,是怎么让线程池终止的?
    一、shundown则是通过在interruptIdleWorkers() 方法调用inturrupt() 方法使得workQueue.take() 被打断,再判断为打断状态返回null,到runworker() 方法里面去。
    这里有几种情况:
    1.就是线程阻塞在workQueue.take() 的时候,就是等待从队列中取出任务的时候,是会直接被调用inturrupt() 方法打断的,因为在等队列就说明目前队列中已经没有任务可以执行了,那么就可以直接通过processWorkerExit() 移除这个worker 了。
    2.就是interruptIdleWorkers() 执行的时候,这个线程正在执行任务,那么就不能trylock() 成功,它就不会执行inturrupt() 方法,而是继续执行下去,并且在队列中不为空的情况下,会一直从队列里面取出任务来执行,直到队列为空,它就会返回runworker() 里执行processWorkerExit() 将worker移除。已经在getTask() 代码里注释了,看代码会更容易理解。
    二、shutdownNow使用interruptWorkers来打断线程,所以就算这个线程在执行任务时被阻塞依旧会被打断,然后直接执行processWorkerExit()方法,并且即使这个线程在任务里面被阻塞,也会直接引起中断异常,并且任务执行完以后,不论队列有没有任务,都会直接执行processWorkerExit() 将worker移除。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值