上一篇文章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後退出。
通過上面的代碼可以看到線程池中線程的數目隨着運行隊列中等待調度對象的變化而變化,從而能夠根據負載情況自我調節,真正做到了隨需而動。