最近項目一個項目要結項了,但客戶要求 TPS 能達到上千,而用我寫的代碼再怎么弄成只能達到 30 + 的 TPS,然后我又將代碼中能緩存的都緩存了,能拆分的也都拆分了,拆分時用的線程池來實現的;其實現的代碼主要為以前寫的一篇博客中的實現方式來實現的。如下:
多線程之 futureTask(future,callable) 實例, jdbc 數據多線程查詢https://blog.csdn.net/puhaiyang/article/details/78041046
在其中用到了線程池,為了方便,線程池是采用如下代碼 new 出來的
final?ExecutorService?executorService?=?Executors.newFixedThreadPool(10);??
通過自己仔細想想后,感覺這代碼總有哪里寫得不對,為此特意寫了下 DEMO 代碼來,并用 jmeter 自己跑一下自己測下:
??@RequestMapping(value?=?"doTest")
????public?Object?doTest(@RequestParam(defaultValue?=?"false")?Boolean?shutdown,
?????????????????????????@RequestParam(defaultValue?=?"10")?Integer?threadCount,
?????????????????????????@RequestParam(defaultValue?=?"100")?Integer?sleepTime,
?????????????????????????@RequestParam(defaultValue?=?"10")?Integer?queryCount)?{
????????long?beginTime?=?System.currentTimeMillis();
????????final?ExecutorService?executorService?=?Executors.newFixedThreadPool(threadCount);
????????for?(int?i?=?0;?i?????????????int?finalI?=?i;
????????????Callable?callable?=?new?Callable()?{
????????????????@Override
????????????????public?Integer?call()?throws?Exception?{
????????????????????Thread.sleep(sleepTime);
????????????????????logger.debug("index:{}?threadInfo:{}",?finalI,?Thread.currentThread().toString());return?finalI;
????????????????}
????????????};
????????????FutureTask?futureTask?=?new?FutureTask(callable);
????????????executorService.submit(futureTask);
????????}if?(shutdown)?{
????????????executorService.shutdown();
????????}
????????Long?endTime?=?System.currentTimeMillis();
????????endTime?=?endTime?-?beginTime;
????????logger.info("info:{}",?endTime);return?atomicInteger.addAndGet(endTime.intValue())?+?"???this:"?+?endTime;
????}?
代碼如上所示,然后我用 jmeter 對此進行了測試,測試 1000 個請求去訪問,每個任務線程休眠時間設的為 1 秒,TPS 為 20 多。
一想這確實挺低的,然后分析其原因,想著是不是 springBoot 的線程數給的太少了,于是乎又把 tomcat 的最大線程數進行了修改,由默認的 200 修改為了 500,但發現沒啥大的變化,想了想后,可能問題不是 tomcat 的配置導致的。
server:
??tomcat:
????max-threads:?500?
然后又通過 Java VisualVM 工具看了看線程信息,沒發現啥問題。
然后出去靜了靜,聽了一兩首音樂后想著起來 Executors 還有一個 newCachedThreadPool() 的用法,它與 newFixedThreadPool() 的區別通過源碼可以大概知道個一二:
newFixedThreadPool:
????/**
?????*?Creates?a?thread?pool?that?reuses?a?fixed?number?of?threads
?????*?operating?off?a?shared?unbounded?queue.??At?any?point,?at?most
?????*?{@code?nThreads}?threads?will?be?active?processing?tasks.
?????*?If?additional?tasks?are?submitted?when?all?threads?are?active,
?????*?they?will?wait?in?the?queue?until?a?thread?is?available.
?????*?If?any?thread?terminates?due?to?a?failure?during?execution
?????*?prior?to?shutdown,?a?new?one?will?take?its?place?if?needed?to
?????*?execute?subsequent?tasks.??The?threads?in?the?pool?will?exist
?????*?until?it?is?explicitly?{@link?ExecutorService#shutdown?shutdown}.
?????*
?????*?@param?nThreads?the?number?of?threads?in?the?pool
?????*?@return?the?newly?created?thread?pool
?????*?@throws?IllegalArgumentException?if?{@code?nThreads?<=?0}
?????*/
????public?static?ExecutorService?newFixedThreadPool(int?nThreads)?{
????????return?new?ThreadPoolExecutor(nThreads,?nThreads,
??????????????????????????????????????0L,?TimeUnit.MILLISECONDS,
??????????????????????????????????????new?LinkedBlockingQueue());
????}
newCachedThreadPool:
????/**
?????*?Creates?a?thread?pool?that?creates?new?threads?as?needed,?but
?????*?will?reuse?previously?constructed?threads?when?they?are
?????*?available.??These?pools?will?typically?improve?the?performance
?????*?of?programs?that?execute?many?short-lived?asynchronous?tasks.
?????*?Calls?to?{@code?execute}?will?reuse?previously?constructed
?????*?threads?if?available.?If?no?existing?thread?is?available,?a?new
?????*?thread?will?be?created?and?added?to?the?pool.?Threads?that?have
?????*?not?been?used?for?sixty?seconds?are?terminated?and?removed?from
?????*?the?cache.?Thus,?a?pool?that?remains?idle?for?long?enough?will
?????*?not?consume?any?resources.?Note?that?pools?with?similar
?????*?properties?but?different?details?(for?example,?timeout?parameters)
?????*?may?be?created?using?{@link?ThreadPoolExecutor}?constructors.
?????*
?????*?@return?the?newly?created?thread?pool
?????*/
????public?static?ExecutorService?newCachedThreadPool()?{
????????return?new?ThreadPoolExecutor(0,?Integer.MAX_VALUE,
??????????????????????????????????????60L,?TimeUnit.SECONDS,
??????????????????????????????????????new?SynchronousQueue());
????}
newFixedThreadPool 是創建一個大小固定的線程池,線程數固定,也不會被回收
newCachedThreadPool 是創建一個大小為 MAX_VALUE 的線程數,并具有緩存功能,如果 60 秒內線程一直 處于空閑,則會進行回收
另外,線程池的 shutdown 方法 doc 文檔的解釋如下:
“Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down. This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.
”
指的是等待線程池執行 shutdown 方法后就不再接收新的執行目標了,等當前線程池中的現場執行完畢后,此線程池就會關閉掉了。
通過查看 JAVA 源碼中的注釋信息后才得知原來我之前寫的代碼有了一個大大的 BUG,不應該執行完一次后就立即把線程池給 shutdown 掉,這樣的話,線程池的意義就沒多大的意思了,跟 new Thread 的就差不多了。尷尬了!
然后將測試代碼修改為如下的代碼:
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.web.bind.annotation.RequestMapping;
import?org.springframework.web.bind.annotation.RequestParam;
import?org.springframework.web.bind.annotation.RestController;
?
import?java.util.concurrent.Callable;
import?java.util.concurrent.ExecutorService;
import?java.util.concurrent.Executors;
import?java.util.concurrent.FutureTask;
import?java.util.concurrent.atomic.AtomicInteger;
?
@RestController
@RequestMapping(value?=?"test")
public?class?TestController?{
????private?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
?
????private?AtomicInteger?atomicInteger?=?new?AtomicInteger(0);
????final?ExecutorService?executorService?=?Executors.newCachedThreadPool();
?
????@RequestMapping(value?=?"doTest")
????public?Object?doTest(@RequestParam(defaultValue?=?"false")?Boolean?shutdown,
?????????????????????????@RequestParam(defaultValue?=?"10")?Integer?threadCount,
?????????????????????????@RequestParam(defaultValue?=?"100")?Integer?sleepTime,
?????????????????????????@RequestParam(defaultValue?=?"10")?Integer?queryCount)?{
????????long?beginTime?=?System.currentTimeMillis();
//????????final?ExecutorService?executorService?=?Executors.newFixedThreadPool(threadCount);
????????for?(int?i?=?0;?i?????????????int?finalI?=?i;
????????????Callable?callable?=?new?Callable()?{
????????????????@Override
????????????????public?Integer?call()?throws?Exception?{
????????????????????Thread.sleep(sleepTime);
????????????????????logger.debug("index:{}?threadInfo:{}",?finalI,?Thread.currentThread().toString());return?finalI;
????????????????}
????????????};
????????????FutureTask?futureTask?=?new?FutureTask(callable);
????????????executorService.submit(futureTask);
????????}if?(shutdown)?{
????????????executorService.shutdown();
????????}
????????Long?endTime?=?System.currentTimeMillis();
????????endTime?=?endTime?-?beginTime;
????????logger.info("info:{}",?endTime);return?atomicInteger.addAndGet(endTime.intValue())?+?"???this:"?+?endTime;
????}
}
調用時,shutdown 傳入 false,并且線程池的 new 方法放到上面的公共方法區域中,而不應該是來一個請求就 new 一個線程池出來。然后將同樣的請求用 jmeter 測試后,發現能達到 300 多了,比之前的 20 多提升了許多倍!
總結:
通過上面的測試,發現了一個我之前用錯的 JAVA 線程池的用法,通過 jmeter 工具測試后才知道其性能是如何的大。
同時在通過修改 springboot 的配置信息后,發現 springBoot 能創建的線程池最大線程數也與其 tomcat 的最大線程數有關,具體身體關系還得靠后面的慢慢探索了。(賊尷尬,這么基礎的代碼問題居然給犯下了這么大一個錯誤. 還好及時地修改了。哈哈)
作者:水中加點糖
來源鏈接:
https://blog.csdn.net/puhaiyang/article/details/80530495