在jdk1.5后,官方为我们实现了线程池ThreadPoolExecutor,通过该线程池,我们可以一次性(其实并非一次性,而是慢慢的添加线程,直到线程池满)的预先执行代价高昂的线程分配,而且所有分配的线程都是可重用的。下面我们将会根据Executors.newFixedThreadPool为切入口分析以下线程池的创建过程和线程池的线程分配问题。
当我们执行Executors.newFixedThreadPool时,我们一直跟进构造这个方法的内部,可以发现它其实是调用了ThreadPoolExecutor的构造函数:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }ThreadPoolExecutor为线程池对象,负责构建恰当的上下文来执行Runnable对象,同时负责管理所有它创建的线程的生命周期。在这个构造函数中,我们只要关注两个参数。nThreads代表的线程池的核心的线程数量(还有一个最大线程数量,这里和核心线程数量保持了一致),LinkedBlockingQueue阻塞队列对象则用来管理所有的任务并且将任务提交给工作线程(只有在当前线程池已满的情况下,才回将一个task放入阻塞队列)。
构造方法执行完成后,我们将得到一个ThreadPoolExecutor对象。注意,此时该对象不会开启任何线程。该对象中有一个重要的属性,分别如下:
1.private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));该字段代表线程池的控制状态,这个字段有两个概念上的属性:1.workercount(代表有效的线程的数量);2.runState(代表线程池的工作状态,running, shutting down etc)。
2.private final BlockingQueue<Runnable> workQueue;用于存放task,并将task提交给工作线程(上面的阻塞队列参数注入位置)
3.private final HashSet<Worker> workers = new HashSet<Worker>();代表所有工作任务的集合。Woker是一个继承了Runnable的类,内部有两个属性final Thread thread和Runnable firstTask,当要开启一个线程时,就会通过worker.get thread获取该内部创建的线程(由于该thread对象创建时,worker将自身通过this作为参数传递给thread,因此开启线程后,运行的还是worker的run方法,而在run方法内部其实是调用的当前worker对象的firsttask对象)
下面我们来看看向ThreadPoolExecutor提交一个任务的时候的具体细节:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); 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); }通过这段代码我们可以看出,当添加一个Runnable任务时,一共有四种情况:
1.该任务对象为null,抛出空指针异常
2.如果当前工作线程的数量小于核心线程的数量,那么就直接调用addWorker(具体细节下文描述),新建一个线程来执行任务,并将线程所在的类Worker加入线程池,如果成功,则直接返回
3.如果当前工作线程的数量大于或等于核心线程数量,且线程池仍处于运行状态,则将该任务成功添加进阻塞队列,成功后进行二次检查该线程池是否可用,如果不可用,那么从阻塞队列中移除该任务,如果此时该阻塞队列移除任务成功,则为当前被拒绝的任务调用处理程序。如果线程池处于不可用状态但阻塞队列移除失败或者线程池处于可用状态,此时如果该线程池的有效的线程数量为0,则执行addworker方法并传递一个空任务。
4.如果当前工作线程的数量大于或等于核心线程数量+阻塞队列的size(默认为无界),那么直接调用addworker方法创建一个新线程来执行该任务,如果传递给addWorker的布尔参数为true,则说明如果当前工作线程数必须小于最大线程池大小(maximumPoolSize),如果为false,必须小于最大线程大小(corePoolSize),如果返回值为false,直接拒绝这个任务请求。
看完了上面四种情况,现在的关注点在于addworker(Runnable task, boolean core)的具体实现,下面我们来看这段代码实现:
private boolean addWorker(Runnable firstTask, boolean core) { ............//省略的代码,主要是一些校验 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); 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(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
第一段省略的代码块主要是用于判断和检查当前线程池是否可用,任务是否为null,有效线程数量是否超过核心线程数量或最大线程数量(根据core不同而不同)等等。这里我们主要分析第二段功能代码。这段代码,首先定义了两个boolean类型的变量,用于判断Worker(见上)任务是否开启,以及创建的Worker对象是否添加进线程池中(本质是hashset集合). 接着传递task给Worker构造器创建了Worker对象,并获得持有Worker对象内部的Thread对象。此时如果线程池的状态为可用或者线程池的状态为关闭但task为null,则对获取的thread的对象进行判断,因为此时还未将Worker添加进线程池中,此时如果thread线程处于存活状态(调用了start方法),则抛出线程状态异常错误,否则,将之前创建的Woker对象添加进线程池。此时workerAdded属性为true。开始执行t.start方法,线程开始启动,此时workerStarted设置为true。如果执行完这些代码,由于抛出异常被finally捕获等原因造成workerStarted仍然为false,则线程池workers移除添加进来的worker对象,并以CAS操作减少当前的有效的线程数量。
上面只是讲解了以下各种情况下线程的创建以及动态地将线程添加进线程池中。回顾一下,线程池的作用是帮助我们避免手动的创建和开启线程,控制线程总数,但还有很重要的一点,复用先前创建的线程,避免频繁的创建新线程提升消耗。而这段功能的体现主要就是在Worker类的run方法中:
/** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); }下面看runWoker方法:
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { **note: while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt 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 { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
这里首先是获得当前Worker对象的firstTask对象(我们一开始构建Worker对象传进来的实现Runnable接口的task任务对象),获取之后,就可以将Worker对象的firstTask引用指向null。下面看我用note标记的那行代码,这是一个while循环,它会先判断当前的task是否为null,如果不是,就直接执行下面的代码,执行顺序为:beforeExecute -> task.run() -> afterExecute; 如果当前task为null,那么它就会调用getTask()方法,从阻塞队列中获取task对象(1.之前讲的第三种情况添加进来的;2.getTask的详细实现细节请查看ThreadPoolExecutor类的1039行),如果此时得到的task依然为空,就调用processWorkerExit(w, completedAbruptly)来将当前线程移除出线程池。由于这是一段循环代码,因此只要阻塞队列中存在任务,那么Woker就会不断的取出任务执行,而不会创建新的线程,这就达到了线程重用的目的。
现在还有最后一个问题,如果当前没有可执行的任务,那么这些空闲的线程如何处理(超时处理)?这里要分两种情况,如果当前线程总数没有超过corePoolSize,默认永远不会回收当前线程池中的线程, 除非allowCoreThreadTimeOut属性设置为true; 如果当前线程总数超过corePoolSize,且当前阻塞队列为空,没有可执行的task,那么此时执行poll(keepAliveTime, TimeUnit.NANOSECONDS)操作,因为此时阻塞队列为空,那么必然阻塞keepAliveTime的时间,timeout设置为true,继续循环,此时会进入我用note标记的代码块,此时会使用CAS操作减少当前执行的线程总数,并返回null,外层方法的processWorkerExit(w, completedAbruptly)方法就会回收该线程。这段代码主要实现如下:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { ...... boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //wc代表线程池中所有Worker实例个数 note: 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; } } }
目前为止,创建线程池,动态地将线程添加进线程池,线程的复用,超时线程的处理都已经介绍完毕。后面有时间,我会根据经验和书籍介绍一下java中不同线程池的状态和各种不同线程池的区别。