上一篇文章http://njulinq.blog.51cto.com/1257169/283585 中介绍了OpenNMS中线程池的结构和构成,下面我们就来看一下这个线程池是怎么运作的。
线程池类RunnableConsumerThreadPool本身提供的接口很少,主要包括getRunQueue(),start((),stop()等,其他暂时不涉及的就不介绍了,有兴趣的可以自行去阅读相关代码。
从上一篇文章中,知道当构造出一个线程池对象后,就可以向该线程池添加等待调度对象,例如:
- m_runner = new RunnableConsumerThreadPool("Test Pool", 0.6f, 1.0f,10);
- m_runner.start();
- m_runner.getRunQueue().add(runnable);
上述代码中,m_runner被构造出来后,要先启动线程池,即调用其start方法,然后可以开始向线程池的运行队列中添加调度对象了,这里的运行队列在上文中介绍过就是一个先进先出的队列。而调度对象实际上就是实现了Runnable接口的对象。
RunnableConsumerThreadPool的核心其实就在于这个运行队列,它对应类SizingFifoQueue,这个类主要提供了三个方法add(),remove(),adjust()。在调用这个类的add或者remove方法时,都会触发对adjust方法的调用。其核心也就是这个adjust方法,下面看下这个adjust方法。
- private void adjust() {
- int e = size();
- synchronized (m_fibers) {
- int alive = livingFiberCount();
- float ratio = (float) e / (float) (alive <= 0 ? 1 : alive);
- // Never stop the last thread!?
- if (alive > 1 && ratio <= m_loRatio) {
- /*
- * If:
- * 1) Fibers greater than one, and...
- * 2) ratio less than low water mark
- */
- Fiber f = null;
- int last = Fiber.START_PENDING;
- for (Fiber fiber : m_fibers) {
- if (fiber != null) {
- switch (fiber.getStatus()) {
- case Fiber.RUNNING:
- if (last < Fiber.RUNNING) {
- f = fiber;
- last = f.getStatus();
- }
- break;
- case Fiber.STOP_PENDING:
- if (last < Fiber.STOP_PENDING) {
- f = null;
- last = Fiber.STOP_PENDING;
- }
- break;
- }
- }
- }
- if (f != null && f.getStatus() != Fiber.STOP_PENDING) {
- if (log().isDebugEnabled()) {
- log().debug("adjust: calling stop on fiber " + f.getName());
- }
- f.stop();
- }
- } else if (((alive == 0 && e > 0) || ratio > m_hiRatio) && alive < m_maxSize) {
- /*
- * If:
- * 1a) Fibers equal to zero and queue not empty, or..
- * 1a) ratio greater than hiRatio, and...
- * 2) Fibers less than max size
- */
- for (int x = 0; x < m_fibers.length; x++) {
- if (m_fibers[x] == null || m_fibers[x].getStatus() == Fiber.STOPPED) {
- Fiber f = new FiberThreadImpl(m_poolName + "-fiber" + x);
- f.start();
- m_fibers[x] = f;
- if (log().isDebugEnabled()) {
- log().debug("adjust: started fiber " + f.getName() + " ratio = " + ratio + ", alive = " + alive);
- }
- break;
- }
- }
- }
- }
- }
这段代码对于理解线程池的运行非常重要,所以需要重点讲解一下:
首先其实是需要计算当前运行队列中等待调度对象与线程池中线程数的比值,即第5行中的代码。这里注意,如果当前没有任何线程,则取线程数为1。如果当线程数大于1并且当前比值小于等于低水印值时,那么这种情况代表什么呢?其实就意味着当前等待调度对象已经远远小于线程池中线程的个数,所以需要收回某些线程。第15-33行的循环其实就是在找当前线程池中状态为running的第一个线程,如果找到这个线程,且当前池中没有将要stop的线程即状态为STOP_PENDING的线程,则将该线程停掉。如果这个线程池里面已经有等待停止的线程,则不需要再去额外停止一个将要运行或正在运行的线程。
相反,如果说目前线程池中没有线程,并且存在等待调度对象,或者说等待调度对象与线程数比值已经超过高水印阈值,在这两种情况下,只要当前线程数没有超过线程池最大线程数,则新启动一个线程。
其实代码中的Fiber就对应于一个线程,当它被创建出来后,就不段的去线程池运行队列中取等待调度对象然后执行,当运行队列中没有调度对象时,该线程等待500毫秒,然后重新去取。如果该线程被停止,则将状态标记为STOPPED后退出。
通过上面的代码可以看到线程池中线程的数目随着运行队列中等待调度对象的变化而变化,从而能够根据负载情况自我调节,真正做到了随需而动。