Java 7之多线程线程池 - 线程池原理(2)

来源:互联网 发布:cs1.5百度软件 编辑:程序博客网 时间:2024/05/01 11:20

线程池能够复用线程,减少线程创建,销毁,恢复等状态切换的开销,提高程序的性能。一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。

ThreadPoolExecutor类中定义了一些与线程状态与活动线程数相关的一些变量,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));  
  2.    // 将整型的24位分为高3位和低29位,高3位表示线程池的状态,低29位表示活动的线程数  
  3.    private static final int COUNT_BITS = Integer.SIZE - 3;  
  4.    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// 29位能表示的最大二进制整数,也就是活动线程数  
  5.   
  6.    // 高3位数值代表的线程池状态  
  7.    private static final int RUNNING    = -1 << COUNT_BITS;    // running 线程池能接受新任务  
  8.    private static final int SHUTDOWN   =  0 << COUNT_BITS;    // shutdown 线程池不再接受新任务  
  9.    private static final int STOP       =  1 << COUNT_BITS;    // stop 线程池不再接受新任务,不再执行队列中的任务,而且要中断正在处理的任务  
  10.    private static final int TIDYING    =  2 << COUNT_BITS;    // tidying 线程池所有任务均已终止  
  11.    private static final int TERMINATED =  3 << COUNT_BITS;    // terminated terminated()方法执行结束  

由如上可知:

ctl是一个AtomicInteger类型的原子对象。ctl记录了"线程池中的任务数量"和"线程池状态"2个信息。ctl共包括32位。其中,高3位表示"线程池状态",低29位表示"线程池中的任务数量"。

RUNNING    -- 对应的高3位值是111SHUTDOWN   -- 对应的高3位值是000STOP       -- 对应的高3位值是001TIDYING    -- 对应的高3位值是010TERMINATED -- 对应的高3位值是011
线程池各个状态之间的切换如下图所示:


线程池各个状态间的转换的详细解释如下所示。

  1> RUNNING(111) -> SHUTDOWN(000) : 调用了shutdown方法,线程池实现了finalize方法,在里面调用了shutdown方法,因此shutdown可能是在finalize中被隐式调用的

  2> (RUNNING(111) or SHUTDOWN(000)) -> STOP(001) 调用了shutdownNow方法

  3> SHUTDOWN(000) -> TIDYING(010) : 当队列和线程池均为空的时候

  4> STOP(001) -> TIDYING(010) : 当线程池为空的时候

  5> TIDYING(010) -> TERMINATED(011) : terminated()方法调用完毕

说明:扩号后的3位数字表示ctl的高3位二进制值,并不关注低29位二进制的值

还有一些对常量的操作方法,只说明部分,其他的有兴趣自己可以去查看,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. private static int runStateOf(int c)     { return c & ~CAPACITY; }  // 得到线程运行状态  
  2. private static int workerCountOf(int c)  { return c & CAPACITY; }   // 得到活动线程数  
  3. private static int ctlOf(int rs, int wc) { return rs | wc; }        // 得到两者表示的值  

来看一下ThreadPoolExecutor()中最主要的一个构造函数,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                           int maximumPoolSize,  
  3.                           long keepAliveTime,  
  4.                           TimeUnit unit,  
  5.                           BlockingQueue<Runnable> workQueue,  
  6.                           ThreadFactory threadFactory,  
  7.                           RejectedExecutionHandler handler) {  
  8.     if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize ||  keepAliveTime < 0)  
  9.         throw new IllegalArgumentException();  
  10.     if (workQueue == null || threadFactory == null || handler == null)  
  11.         throw new NullPointerException();  
  12.     this.corePoolSize = corePoolSize;  
  13.     this.maximumPoolSize = maximumPoolSize;  
  14.     this.workQueue = workQueue;  
  15.     this.keepAliveTime = unit.toNanos(keepAliveTime);  
  16.     this.threadFactory = threadFactory;  
  17.     this.handler = handler;  
  18. }  

调用Executors方法中的几个方法,如newCachedThreadPool()、newFixedThreadPool()时,都会间接调用上面的构造方法来初始化所有的线程池相关变量。


1、创建线程池并执行任务


有了Executor对象后,就可以调用execute()方法执行任务了。方法的源代码如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public void execute(Runnable command) {  
  2.           
  3.         if (command == null)  // 任务为null,则抛出异常  
  4.             throw new NullPointerException();  
  5.         int c = ctl.get();    // 取出记录着runState和workerCount 的 ctl的当前值  
  6.           
  7.         /* 
  8.          *  通过workerCountOf方法从ctl所表示的int值中提取出低29位的值,也就是当前活动的线程数。如果当前 
  9.          *  活动的线程数少于corePoolSize,则通过addWorker(command, true)新建一个线程,并将任务(command) 
  10.          *  添加到该线程中 
  11.          */  
  12.         if (workerCountOf(c) < corePoolSize) {      
  13.             /* 
  14.              * addWorker()返回值表示: 
  15.              * 1、true 表示需要检测当前运行的线程是否小于corePoolSize 
  16.              * 2、false 表示需要检测当前运行的线程数量是否小于maxPoolSize 
  17.              */  
  18.             if (addWorker(command, true))      
  19.                 return;       // 新线程创建成功,终止该方法的执行  
  20.             c = ctl.get();    // 任务添加到线程失败,取出记录着runState和workerCount 的 ctl的当前值  
  21.         }  
  22.         /* 
  23.          * 方法解释: 
  24.          * isRunning(c) 当前线程池是否处于运行状态。源代码是通过判断c < SHUTDOWN 来确定返回值。由于RUNNING才会接收新任务,且只有这个值-1才小于SHUTDOWN 
  25.          * workQueue.offer(command) 任务添加到缓冲队列 
  26.          */  
  27.         if (isRunning(c) && workQueue.offer(command)) {// 当前线程处于运行状态且成功添加到缓冲队列  
  28.             int recheck = ctl.get();   
  29.             /* 
  30.              * 如果 线程池已经处于非运行状态,则从缓冲队列中移除任务然后采用线程池指定的策略拒绝任务 
  31.              * 如果 线程池中任务数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null 
  32.              */  
  33.             if (! isRunning(recheck) && remove(command))      
  34.                 reject(command);                             
  35.             else if (workerCountOf(recheck) == 0)  // 得到活动线程数为0          
  36.                 addWorker(nullfalse);  
  37.         }  
  38.         /* 
  39.          * 当不满足以下两个条件时执行如下代码: 
  40.          *  1. 当前线程池并不处于Running状态    
  41.          *  2. 当前线程池处于Running状态,但是缓冲队列已经满了   
  42.          */  
  43.         else if (!addWorker(command, false))  
  44.             reject(command);   // 采用线程池指定的策略拒绝任务  
  45.   }  

 当前活动的线程小于corePoolSize了,那么等于和大于corePoolSize怎么处理呢?

      1>  当前活动的线程数量 >= corePoolSize 的时候,都是优先添加到队列中,直到队列满了才会去创建新的线程,在这里第20行的if语句已经体现出来了。这里利用了&&的特性,只有当第一个条件会真时才会去判断第二个条件,第一个条件是isRunning(),判断线程池是否处于RUNNING状态,因为只有在这个状态下才会接受新任务,否则就拒绝,如果正处于RUNNING状态,那么就加入队列,如果加入失败可能就是队列已经满了,这时候直接执行第29行。 

  2> 在execute()方法中,当 当前活动的线程数量 < corePoolSize 时,会执行addWorker()方法,关于addWorker(),它是用来直接新建线程用的,之所以叫addWorker而不是addThread是因为在线程池中,所有的线程都用一个Worker对象包装着,来看一下这个方法:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * 创建并执行新线程 
  3.   * @param firstTack 用于指定新增的线程执行的第一个任务 
  4.   * 
  5.   * @param core      true表示在新增线程时会判断当前活动线程数是否少于corePoolSize, 
  6.   *                  false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize 
  7.   * 
  8.   * @return 是否成功新增一个线程 
  9.   */  
  10.  private boolean addWorker(Runnable firstTask, boolean core) {  
  11.       retry:  
  12.       for (;;) {  
  13.           int c = ctl.get();         // 获取记录着runState和workCount的int变量的当前值  
  14.           int rs = runStateOf(c);    // 获取当前线程池运行的状态  
  15.           /* 
  16.             这个条件代表着以下几个情景,就直接返回false说明线程创建失败: 
  17.             1.rs > SHUTDOWN; 此时不再接收新任务,且所有的任务已经执行完毕 
  18.             2.rs = SHUTDOWN; 此时不再接收新任务,但是会执行队列中的任务,在后买年的或语句中,第一个不成立,firstTask != null成立 
  19.             3.rs = SHUTDOWN;此时不再接收新任务,fistTask == null,任务队列workQueue已经空了 
  20.           */  
  21.           if (rs >= SHUTDOWN &&  
  22.               ! (rs == SHUTDOWN &&  
  23.                  firstTask == null &&  
  24.                  ! workQueue.isEmpty()))  
  25.               return false;  
  26.   
  27.           for (;;) {  
  28.               //获取当前活动的线程数  
  29.               int wc = workerCountOf(c);      
  30.               //先判断当前活动的线程数是否大于最大值,如果超过了就直接返回false说明线程创建失败  
  31.               //如果没有超过再根据core的值再进行以下判断  
  32.               /* 
  33.                   1.core为true,则判断当前活动的线程数是否大于corePoolSize 
  34.                   2.core为false,则判断当前活动线程数是否大于maximumPoolSize 
  35.               */  
  36.               if (wc >= CAPACITY ||  
  37.                   wc >= (core ? corePoolSize : maximumPoolSize))  
  38.                   return false;  
  39.               //比较当前值是否和c相同,如果相同,则改为c+1,并且跳出大循环,直接执行Worker进行线程创建  
  40.               if (compareAndIncrementWorkerCount(c))  
  41.                   break retry;  
  42.               c = ctl.get();  // 获取ctl的当前值  
  43.               if (runStateOf(c) != rs)    //检查下当前线程池的状态是否已经发生改变  
  44.                   continue retry;    //如果已经改变了,则进行外层retry大循环,否则只进行内层的循环  
  45.               // else CAS failed due to workerCount change; retry inner loop  
  46.           }  
  47.       }  
  48.       //下面这里就是开始创建新的线程了  
  49.       //Worker的也是Runnable的实现类  
  50.       Worker w = new Worker(firstTask);  
  51.       //因为不可以直接在Worker的构造方法中进行线程创建  
  52.       //所以要把它的引用赋给t方便后面进行线程创建  
  53.       Thread t = w.thread;      
  54.   
  55.       final ReentrantLock mainLock = this.mainLock;  
  56.       mainLock.lock();  
  57.       try {  
  58.           
  59.           //再次取出ctl的当前值,用于进行状态的检查,防止线程池的已经状态改变了  
  60.           int c = ctl.get();  
  61.           int rs = runStateOf(c);  
  62.   
  63.           //将if语句中的条件转换为一个等价实现 :t == null || (rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null))  
  64.           //有个t == null是因为如果使用的是默认的ThreadFactory的话,那么它的newThread()可能会返回null  
  65.           /* 
  66.             1. 如果t == null, 则减少一个线程数,如果线程池处于的状态 > SHUTDOWN,则尝试终止线程池 
  67.             2. 如果t != null,且rs == SHUTDOWN,则不再接收新任务,若firstTask != null,则此时也是返回false,创建线程失败 
  68.             3. 如果t != null, 且rs > SHUTDOWN,同样不再接受新任务,此时也是返回false,创建线程失败 
  69.           */  
  70.           if (t == null ||  
  71.               (rs >= SHUTDOWN &&  
  72.                ! (rs == SHUTDOWN &&  
  73.                   firstTask == null))) {  
  74.               decrementWorkerCount();    //减少一个活动的当前线程数  
  75.               tryTerminate();    //尝试终止线程池  
  76.               return false;    //返回线程创建失败  
  77.           }  
  78.   
  79.           workers.add(w);    //将创建的线程添加到workers容器中  
  80.   
  81.           int s = workers.size();    //获取当前线程活动的数量  
  82.           if (s > largestPoolSize)    //判断当前线程活动的数量是否超过线程池最大的线程数量  
  83.               largestPoolSize = s;    //当池中的工作线程创新高时,会将这个数记录到largestPoolSize字段中。然后就可以启动这个线程t了  
  84.       } finally {  
  85.           mainLock.unlock();  
  86.       }  
  87.   
  88.       t.start();    //开启线程  
  89.       //若start后,状态又变成了SHUTDOWN状态(如调用了shutdownNow方法)且新建的线程没有被中断过,  
  90.       //就要中断该线程(shutdownNow方法要求中断正在执行的线程),  
  91.       //shutdownNow方法本身也会去中断存储在workers中的所有线程  
  92.       if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())  
  93.           t.interrupt();  
  94.   
  95.       return true;  
  96.   }  

那么在创建线程的时候,线程执行的是什么的呢?

  我们前面提到Worker继承的其实也是Runnable,它在创建线程的时候是以自身作为任务传进先创建的线程中的,这段比较简单,我就不一一注释了,只是给出源代码给大家看吧。

      Worker(Runnable firstTask) {            this.firstTask = firstTask;            //this指的是worker对象本身            this.thread = getThreadFactory().newThread(this);        }

    它以自身的对象作为线程任务传进去,那么它的run方法又是怎样的呢?

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

   竟然只有一句话调用runWorker()方法,这个可是重头戏,我们来看看,究竟运行的是什么。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 执行Worker中的任务,它的执行流程是这样的: 
  3.  * 若存在第一个任务,则先执行第一个任务,否则,从队列中拿任务,不断的执行, 
  4.  * 直到getTask()返回null或执行任务出错(中断或任务本身抛出异常),就退出while循环。 
  5.  * @param w woker 
  6.  */  
  7.  final void runWorker(Worker w) {  
  8.         Runnable task = w.firstTask;    //将当前Worker中的任务取出来交给task,并释放掉w.firstTask占用的内存  
  9.         w.firstTask = null;  
  10.         //用于判断线程是否由于异常终止,如果不是异常终止,在后面将会将该变量的值改为false  
  11.         //该变量的值在processWorkerExit()会使用来判断线程是否由于异常终止  
  12.         boolean completedAbruptly = true;      
  13.         try {  
  14.             //执行任务,直到getTask()返回的值为null,在此处就相当于复用了线程,让线程执行了多个任务  
  15.             while (task != null || (task = getTask()) != null) {      
  16.                 w.lock();  
  17.                 clearInterruptsForTaskRun();//对线程池状态进行一次判断,后面我们会讲解一下该方法  
  18.                 try {  
  19.                     beforeExecute(w.thread, task);    //在任务执行前需要做的逻辑方法,该方面可以由用户进行重写自定义  
  20.                     Throwable thrown = null;  
  21.                     try {  
  22.                         task.run();    //开始执行任务  
  23.                     } catch (RuntimeException x) {  
  24.                         thrown = x; throw x;  
  25.                     } catch (Error x) {  
  26.                         thrown = x; throw x;  
  27.                     } catch (Throwable x) {  
  28.                         thrown = x; throw new Error(x);  
  29.                     } finally {  
  30.                         afterExecute(task, thrown);    //在任务执行后需要做的逻辑方法,该方面可以由用户进行重写自定义  
  31.                     }  
  32.                 } finally {  
  33.                     task = null;      
  34.                     w.completedTasks++;    //增加该线程完成的任务  
  35.                     w.unlock();  
  36.                 }  
  37.             }  
  38.             completedAbruptly = false;    //线程不是异常终止  
  39.         } finally {  
  40.             processWorkerExit(w, completedAbruptly);    //结束该线程  
  41.         }  
  42.     }  
下面就是线程在执行任务之前对线程池状态的一次判断:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.    * 对线程的结束做一些清理和数据同步 
  3.    * @param w 封装线程的Worker 
  4.    * @param completedAbruptly 表示该线程是否结束于异常 
  5.    */  
  6.   private void processWorkerExit(Worker w, boolean completedAbruptly) {  
  7.       // 如果completedAbruptly值为true,则说明线程是结束于异常  
  8.       //如果不是结束于异常,那么它降在runWorker方法的while循环中的getTask()方法中已经减一了  
  9.       if (completedAbruptly)   
  10.           decrementWorkerCount();    //此时将线程数量减一  
  11.   
  12.       final ReentrantLock mainLock = this.mainLock;  
  13.       mainLock.lock();  
  14.       try {  
  15.           completedTaskCount += w.completedTasks;    //统计总共完成的任务数  
  16.           workers.remove(w);    //将该线程数从workers容器中移除  
  17.       } finally {  
  18.           mainLock.unlock();  
  19.       }  
  20.   
  21.       tryTerminate();    //尝试终止线程池  
  22.   
  23.       int c = ctl.get();  
  24.       //接下来的这个if块要做的事儿了。当池的状态还是RUNNING,  
  25.       //又要分两种情况,一种是异常结束,一种是正常结束。异常结束比较好弄,直接加个线程替换死掉的线程就好了,  
  26.       //也就是最后的addWorker操作  
  27.       if (runStateLessThan(c, STOP)) {    //如果当前运行状态为RUNNING,SHUTDOWN  
  28.           if (!completedAbruptly) {    //如果线程不是结束于异常  
  29.               int min = allowCoreThreadTimeOut ? 0 : corePoolSize;    //是否允许线程超时结束  
  30.               if (min == 0 && ! workQueue.isEmpty())    //如果允许把那个且队列不为空  
  31.                   min = 1;    //至少要保留一个线程来完成任务  
  32.               //如果当前活动的线程数大于等于最小的值  
  33.               // 1.不允许核心线程超时结束,则必须要使得活动线程数超过corePoolSize数才可以  
  34.               // 2. 允许核心线程超时结束,但是队列中有任务,必须留至少一个线程  
  35.               if (workerCountOf(c) >= min)      
  36.                   return// replacement not needed  
  37.           }  
  38.           //直接加个线程  
  39.           addWorker(nullfalse);      
  40.       }  
  41.   }  
前面我们的方法遇见过很多次tryTerminate()方法,到底他是怎样尝试结束线程池的呢?
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 执行该方法,根据线程池状态进行  判断是否结束线程池 
  3.  */  
  4.  final void tryTerminate() {  
  5.     for (;;) {  
  6.         int c = ctl.get();  
  7.         if (isRunning(c) ||    //线程池正在运行中,自然不能结束线程池啦  
  8.             runStateAtLeast(c, TIDYING) ||    //如果状态为TIDYING或TERMINATED,池中的活动线程数已经是0,自然也不需要做什么操作了  
  9.             (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))    //线程池出于SHUTDOWN状态,但是任务队列不为空,自然不能结束线程池啦  
  10.             return;  
  11.         if (workerCountOf(c) != 0) { // Eligible to terminate  
  12.             /* 
  13.               调用这个方法的目的是将shutdown信号传播给其它线程。 
  14.               调用shutdown方法的时候会去中断所有空闲线程,如果这时候池中所有的线程都正在执行任务, 
  15.               那么就不会有线程被中断,调用shutdown方法只是设置了线程池的状态为SHUTDOWN, 
  16.               在取任务(getTask,后面会细说)的时候,假如很多线程都发现队列里还有任务(没有使用锁,存在竞态条件), 
  17.               然后都去调用take,如果任务数小于池中的线程数,那么必然有方法调用take后会一直等待(shutdown的时候这些线程正在执行任务, 
  18.               所以没能调用它的interrupt,其中断状态没有被设置),那么在没有任务且线程池的状态为SHUTDWON的时候, 
  19.               这些等待中的空闲线程就需要被终止iinterruptIdleWorkers(ONLY_ONE)回去中断一个线程,让其从take中退出, 
  20.               然后这个线程也进入同样的逻辑,去终止一个其它空闲线程,直到池中的活动线程数为0。 
  21.             */  
  22.             interruptIdleWorkers(ONLY_ONE);  
  23.             return;  
  24.         }  
  25.   
  26.         final ReentrantLock mainLock = this.mainLock;  
  27.         mainLock.lock();  
  28.         try {  
  29.             /* 
  30.               当状态为SHUTDOWN,且活动线程数为0的时候,就可以进入TIDYING状态了, 
  31.               进入TIDYING状态就可以执行方法terminated(), 
  32.               该方法执行结束就进入了TERMINATED状态(参考前文中各状态的含义以及可能的状态转变) 
  33.             */  
  34.             if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {  
  35.                 try {  
  36.                     terminated();    //执行该方法,结束线程池  
  37.                 } finally {  
  38.                     ctl.set(ctlOf(TERMINATED, 0));  
  39.                     /* 
  40.                       当线程池shutdown后,外部可能还有很多线程在等待线程池真正结束, 
  41.                       即调用了awaitTermination方法,该方法中,外部线程就是在termination上await的, 
  42.                       所以,线程池关闭之前要唤醒这些等待的线程,告诉它们线程池关闭结束了。 
  43.                     */  
  44.                     termination.signalAll();  
  45.                 }  
  46.                 return;  
  47.             }  
  48.         } finally {  
  49.             mainLock.unlock();  
  50.         }  
  51.         // else retry on failed CAS  
  52.     }  
  53. }  

2、关闭线程池

关闭时使用shutdown()方法,源码如下:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public void shutdown() {  
  2.       final ReentrantLock mainLock = this.mainLock;  
  3.       mainLock.lock();  
  4.       try {  
  5.           checkShutdownAccess();    // 检查终止线程池的线程是否有权限。  
  6.           advanceRunState(SHUTDOWN);// 设置线程池的状态为关闭状态。  
  7.           interruptIdleWorkers();   // 中断线程池中空闲的线程  
  8.           onShutdown();             // 钩子函数,在ThreadPoolExecutor中没有任何动作  
  9.       } finally {  
  10.           mainLock.unlock();  
  11.       }  
  12.       tryTerminate();               // 尝试终止线程池  
  13.   }  
0 0
原创粉丝点击