Java多線程:線程池簡介及線程池之坑

線程池簡介

自JDK1.5,Java吊炸天的併發包就提供線程池java.util.concurrent.ThreadPoolExecutor ,先來看看其各個字段的含義:
corePoolSize 核心線程數,指保留的線程池大小(不超過maximumPoolSize值時,線程池中最多有corePoolSize 個線程工作)。
maximumPoolSize 指的是線程池的最大大小(線程池中最大有corePoolSize 個線程可運行)。
keepAliveTime 指的是空閒線程結束的超時時間(當一個線程不工作時,過keepAliveTime 長時間將停止該線程)。
unit 是一個枚舉,表示 keepAliveTime 的單位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7個可選值)。
workQueue 表示存放任務的隊列(存放需要被線程池執行的線程隊列)。
handler 拒絕策略(添加任務失敗後如何處理該任務).

關於corePoolSize、maxPoolSize、queueCapacity之間的關係: corePoolSize爲初始線程個數,當corePoolSize的線程都在執行中時,則將Runnable臨時放入queueCapacity的緩衝隊列中等待,當queueCapacity滿了時,纔會將線程個數從corePoolSize擴展至maxPoolSize,如果此時queueCapacity緩存隊列任然是滿的,則後續Runnable對象加入其中時就會被abort拋棄。

這位兄弟提供了很好的代碼說明例子,具體可參見:http://dmwdmc.iteye.com/blog/1882475。例子中有模擬整個線程池都滿了拋棄後面任務的場景。

線程池之坑1

說明:如下線程池配置均爲使用的spring封裝過得線程池。
坑出現的場景:一個接口,需要四次次訪問數據庫,而且四次結果之間也沒有必然聯繫,何不把這個四次訪問數據庫操作分別放入四個線程中同時去訪問呢,從而大大節省時間。
OK,樓主就這麼幹了,並且這四個線程都丟到一個統一的線程池中去執行。那麼坑來了,該接口常常於一天的固定時間有着“驚喜”的訪問量,上線第二天,六個tomcat全部掛掉,查看堆dump文件,發現出現了OOM錯誤,而且六個tomcat幾乎相差不多的時間出現。樓主心虛地就想到了自己加的線程池。下面是樓主的線程池配置,各位鑑賞(配置是直接從其他人的配置拷貝過來,沒有多想):

    <bean id="resQueryBaseInfoExecutor"
          class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="threadNamePrefix" value="resQueryBaseInfoExecutor-" />
        <property name="corePoolSize" value="5" />
        <property name="keepAliveSeconds" value="100" />
        <property name="maxPoolSize" value="10" />
        <property name="queueCapacity" value="100000" />
    </bean>

其中queueCapacity被設置爲100000,目的是爲了保證所有任務都沒接受而不會因爲線程池滿了而被拋棄,但顯然當緩存隊列真的到了10W時,OOM應該就不遠了…
樓主深思了原因:corePoolSize過小,導致線程池吞吐量過小,而queueCapacity配置過大,加上吞吐能力差,直接導致等待隊列達到10W個對象,導致JVM順利地OOM了,從而導致整個應用掛掉,造成宕機嚴重問題。

線程池之坑2

corePoolSize配置小,queueCapacity也配置小,導致線程池吞吐能力差,緩存數量少,很有可能在流量稍微上來之後拒絕後面的線程,導致數據操作丟失,這也是樓主親身經歷的:

    <bean id="resQueryBaseInfoExecutor"
          class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="threadNamePrefix" value="resQueryBaseInfoExecutor-" />
        <property name="corePoolSize" value="5" />
        <property name="keepAliveSeconds" value="100" />
        <property name="maxPoolSize" value="10" />
        <property name="queueCapacity" value="500" />
    </bean>

因此綜上,正確的估算接口的流量、以及每個線程任務執行的大概時間,計算出每個線程的吞吐量,才能正確設置corePoolSize、maxPoolSize、queueCapacity三個參數。

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