Java ThreadPoolExecutor 線程池 tips 2:偷借線程

SEDA (Staged event-driven architecture)

論文在此: The Staged Event-Driven Architecture for Highly-Concurrent Server Applications

尚未閱讀,從字面理解

  • 有event, 則有event queue
  • 有event, 則有event handler
  • 有Staged,劃分成不同階段,將任務進行細分,每個細分的階段使用不一樣的線程池配置

租借線程

簡而言之,概而括之,推而廣之,系統中不是單個線程池來運行系統中的任務線程,而是多個線程池來handle不同類型的任務,比如數據庫的連接,文件IO的讀寫,網絡IO的讀寫,對象的序列化等等。

不管是單個線程池還是多個線程池,池的配置毫無疑問影響池的性能和效用。在前一篇文章中,我提到單線程吞吐量來估計系統的線程數目(suggestedNumberOfWorkerThread)。本篇文章會介紹另外一個小技巧,線程的租借。

運行任務

如果當前的線程池(m_jobExecutor)拋出RejectedExecutionExecution,則嘗試從其他線程池(Processor)租借(lendAWorkerThread)

private ThreadPoolExecutor m_jobExecutor;

void tryToExecute(Job job)
{
      try
        {
            m_jobExecutor.execute(job);
        } catch (RejectedExecutionException e)
        {
            // Iterate through all processors starting from random point 
            List allProcessors = getAll();
            
            int startIndex = m_random.nextInt(allProcessors.size());
            int i = startIndex;
            do
            {
                Processor processor = allProcessors.get(i);

                i = (i + 1) % allProcessors.size();
                
                if (processor == this)
                {
                    continue;
                }
                if (processor.lendAWorkerThread(job))
                {
                    // We found a Good Samaritan
                    log.info(getName() + " has lent a worker thread from the "
                            + processor.getName());
                    return;
                }
            } while (i != startIndex); 
            // Nobody helped us, re-throw the exception
            throw e;
        }
}

線程租借條件

如果最大線程池大小和預估使用線程數目(suggestedNumberOfWorkerThread)相差無幾(10%),說明當下線程池也將會很繁忙,不能租借。

如果當前線程池中的已經90%的active (m_jobExecutor.getActiveCount()),說明當下線程池已經很繁忙,不能租借。

    boolean lendAWorkerThread(LocateJob job)
    {
        if (!isStartingOrStarted())
        {
            return false;
        }

        int maxPoolSize = m_jobExecutor.getMaximumPoolSize();

        if (maxPoolSize - getSuggestedNumberOfWorkerThreads() < (maxPoolSize / 10))
        {
            // We do not have capacity
            return false;
        }
        if ((maxPoolSize - m_jobExecutor.getActiveCount()) < (maxPoolSize / 10))
        {
            // We have capacity but we lent (or consumed) almost all of it
            return false;
        }
        try
        {
            m_jobExecutor.execute(job);
        } catch (Throwable e)
        {
            // We couldn't lent a worker thread
            return false;
        }
        
        return true;
    }

線程池什麼時候會拋出RejectedExecutionException?

當線程池使用了有界隊列和有界池,隊列已經被填滿,池中的線程也已經全部在運行。(參見javadoc  reject task)

本文的ThreadPoolExecutor使用SynchronousQueue,只要線程池被耗盡,新增任務就會拋出RejectedExecutionException

  m_jobExecutor = new ThreadPoolExecutor(m_config.getThreadPoolMaxSize(),
                m_config.getThreadPoolMaxSize(), getPollingIntervalInMillis(),
                TimeUnit.MILLISECONDS, new SynchronousQueue(),
                new DaemonThreadFactory(getName()))
        {
            protected void beforeExecute(Thread t, Runnable r)
            {
                super.beforeExecute(t, r);

                try
                {
                    (Job) r).setExecutionThread(t);
                } catch (Throwable e)
                {
                    getLog().error(getName() + ": internal error: " + e);
                }
            }

            protected void afterExecute(Runnable r, Throwable t)
            {
                super.afterExecute(r, t);
            }
        };
        m_jobExecutor.prestartAllCoreThreads();
        m_jobExecutor.allowCoreThreadTimeOut(true);

線程池的動態調整

在下一篇文章,我將介紹一種超級大殺器: sliding windows statistical,根據過去線程的使用狀況,來配置下一個windows窗口的線程池(run time suggestedNumberOfWorkerThread),從而形成彈性線程池(elastic thread pool),在系統訪問高峯時期,自動增加線程池的數目;在peak回落時,自動減少線程池的數目。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章