• 浅谈线程池《一》


    《一》前言

    多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。

    为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。这就是线程池的由来!

    《二》Java通过ThreadPoolExecutor来创建线程池:

    ThreadPoolExecutor th = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
     

     我们可以看到它里面的参数还是挺多的,下面来一一介绍:

    1.corePoolSize:核心线程数,在创建线程池之后,它是默认没有线程在里面的。只有当任务来的时候才会去创建线程执行任务。那么就存在一个问题,当任务量达到这个核心线程树后再进来任务,该如何呢?答案是进队列,也就是说corePoolSize是允许同时运行的最大线程数。

    2.maximumPoolSize:线程池最大的线程数,它表示能创建最大的线程数,maximumPoolSize >= corePoolSize

    3.keepAliveTime:线程没有任务时最多保持多久停止。默认的情况下,只有当达到corePoolSize时才会触发keepAliveTime,也就是说任务达到了corePoolSize后有空闲线程并且达到了keepAliveTime,那就讲这个线程shutdown。

    4.unit:keepAliveTime 的单位。

    5.workQueue:一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。

    6.threadFactory创建线程工厂

    7.handler当任务达到maximumPoolSize+workQueue,任务交给它处理。即表示当拒绝处理任务时的策略。

    《三》这个队列具体用的什么队列呢?

    任务缓存队列:

    workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

    1有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

    2无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

    3直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

    《四》拒绝策略:(handler)

    AbortPolicy:丢弃任务并抛出RejectedExecutionException

    CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

    DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

    DiscardPolicy:丢弃任务,不做任何处理。

    《五》线程池的任务处理策略:

    如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

    如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

    如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

    《六》线程池的关闭:

    ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

    shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

     《七》源码解析:

    构造方法:

    核心方法:public void execute(Runnable command)

     public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            /*
            // //继续3个步骤:
             * Proceed in 3 steps:
             *
             * 1. If fewer than corePoolSize threads are running, try to
             * start a new thread with the given command as its first
             * task.  The call to addWorker atomically checks runState and
             * workerCount, and so prevents false alarms that would add
             * threads when it shouldn't, by returning false.
        
             //1.如果正在运行的线程数小于corePoolSize,那么将调用addWorker 方法来创建一个新的线      
             //程,  
             //并将该任务作为新线程的第一个任务来执行。在创建线程之前会做原子性质的检查,如果条件不允   
            //许,则不创建线程来执行任务,并返回false.
             *
             * 2. If a task can be successfully queued, then we still need
             * to double-check whether we should have added a thread
             * (because existing ones died since last checking) or that
             * the pool shut down since entry into this method. So we
             * recheck state and if necessary roll back the enqueuing if
             * stopped, or start a new thread if there are none.
             *
             //2.如果一个任务成功进入阻塞队列,那么我们需要进行一个双重检查来确保是我们已经添加一个线  
          //程因为存在着一些线程在上次检查后他已经死亡。或者 当我们进入该方法时,该线程池已经关闭。所 
          //以,我们将重新检查状态,线程池关闭的情况下则回滚入队列,线程池没有线程的情况则创建一个新
         //的线程。。
             * 3. If we cannot queue task, then we try to add a new
             * thread.  If it fails, we know we are shut down or saturated
             * and so reject the task.
             */
          //3.如果我们无法排队任务,那么我们尝试添加一个新的*线程。如果失败,我们知道我们关闭或饱和    
         //因此拒绝任务。
    
    
    
          // 1.当前线程数量小于corePoolSize,则创建并启动线程。
            int c = ctl.get();
            if (workerCountOf(c) < corePoolSize) {
                if (addWorker(command, true))
                 //成功
                    return;
                c = ctl.get();
            }
           // 2.步骤1失败,则尝试进入阻塞队列,
            if (isRunning(c) && workQueue.offer(command)) {
             // 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(command))
                    reject(command);
              // 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
           // 3. 步骤1和2失败,则尝试将线程池的数量有corePoolSize扩充至maxPoolSize,如果失败,则 
         //拒绝任务
            else if (!addWorker(command, false))
                reject(command);
        }                
    

    ThreadFactory:

    addWorker方法:

    private boolean addWorker(Runnable firstTask, boolean core) {
            retry:
           // 外层循环,用于判断线程池状态
          // CAS+死循环实现的关于线程池状态,线程数量的校验与更新逻辑
        retry:
            for (;;) {
                int c = ctl.get();
                int rs = runStateOf(c);
    
                // Check if queue empty only if necessary.
                if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                       firstTask == null &&
                       ! workQueue.isEmpty()))
                    return false;
               // 内层的循环,任务是将worker数量加1
                for (;;) {
                    int wc = workerCountOf(c);
                    if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                        return false;
                    if (compareAndIncrementWorkerCount(c))
                        break retry;
                    c = ctl.get();  // Re-read ctl
                    if (runStateOf(c) != rs)
                        continue retry;
                    // else CAS failed due to workerCount change; retry inner loop
                }
            }
         // worker加1后,接下来将woker添加到HashSet<Worker>中,并启动worker
            boolean workerStarted = false;
            boolean workerAdded = false;
            Worker w = null;
            try {
                //把指定任务作为参数新建一个worker线程
                w = new Worker(firstTask);
                //t就是代表woker线程
                final Thread t = w.thread;
                if (t != null) {
                    final ReentrantLock mainLock = this.mainLock;
                    mainLock.lock();
                    try {
                        // Recheck while holding lock.
                        // Back out on ThreadFactory failure or if
                        // shut down before lock acquired.
                        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();
                    }
                    // 如果往HashSet<Worker>添加成功,则启动该线程
                    if (workerAdded) {
                        t.start();
                        workerStarted = true;
                    }
                }
            } finally {
                if (! workerStarted)
                    addWorkerFailed(w);
            }
            return workerStarted;
        }
    

    《八》 Java通过Executors,提供了四种线程池:

    1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    2.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

    3.创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。它的缓存时间默认为60秒,核心线程是Integer.MAX_VALUE,阻塞队列用的是SynchronousQueue<Runnable>(),当线程空闲没有任务时,超过60秒就会关闭这个线程并回收。

    4.创建一个定长线程池,支持定时及周期性任务执行。

  • 相关阅读:
    BZOJ1119: [POI2009]SLO
    BZOJ1486: [HNOI2009]最小圈
    BZOJ1098: [POI2007]办公楼biu
    BZOJ2242: [SDOI2011]计算器
    PAT A1023
    SpringCloud之整合Feign
    SpringCloud之整合Feign
    小程序在wxml页面格式化类似的2019-02-16T10:54:47.831000时间
    小程序在wxml页面格式化类似的2019-02-16T10:54:47.831000时间
    Javascript基础之-var,let和const深入解析(二)
  • 原文地址:https://www.cnblogs.com/youdiaodaxue16/p/11419572.html
Copyright © 2020-2023  润新知