最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

論Java Web應用中調(diào)優(yōu)線程池的重要性

 更新時間:2016年04月01日 11:49:39   投稿:lijiao  
這篇文章主要論述Java Web應用中調(diào)優(yōu)線程池的重要性,通過了解應用的需求,組合最大線程數(shù)和平均響應時間,得出一個合適的線程池配置

不論你是否關(guān)注,Java Web應用都或多或少的使用了線程池來處理請求。線程池的實現(xiàn)細節(jié)可能會被忽視,但是有關(guān)于線程池的使用和調(diào)優(yōu)遲早是需要了解的。本文主要介紹Java線程池的使用和如何正確的配置線程池。

單線程

我們先從基礎開始。無論使用哪種應用服務器或者框架(如Tomcat、Jetty等),他們都有類似的基礎實現(xiàn)。Web服務的基礎是套接字(socket),套接字負責監(jiān)聽端口,等待TCP連接,并接受TCP連接。一旦TCP連接被接受,即可從新創(chuàng)建的TCP連接中讀取和發(fā)送數(shù)據(jù)。

為了能夠理解上述流程,我們不直接使用任何應用服務器,而是從零開始構(gòu)建一個簡單的Web服務。該服務是大部分應用服務器的縮影。一個簡單的單線程Web服務大概是這樣的:

ServerSocket listener = new ServerSocket(8080);
try {
 while (true) {
  Socket socket = listener.accept();
  try {
   handleRequest(socket);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
} finally {
 listener.close();
}

上述代碼創(chuàng)建了一個 服務端套接字(ServerSocket) ,監(jiān)聽8080端口,然后循環(huán)檢查這個套接字,查看是否有新的連接。一旦有新的連接被接受,這個套接字會被傳入handleRequest方法。這個方法會將數(shù)據(jù)流解析成HTTP請求,進行響應,并寫入響應數(shù)據(jù)。在這個簡單的示例中,handleRequest方法僅僅實現(xiàn)數(shù)據(jù)流的讀入,返回一個簡單的響應數(shù)據(jù)。在通常實現(xiàn)中,該方法還會復雜的多,比如從數(shù)據(jù)庫讀取數(shù)據(jù)等。

final static String response =
  “HTTP/1.0 200 OK/r/n” +
  “Content-type: text/plain/r/n” +
  “/r/n” +
  “Hello World/r/n”;

public static void handleRequest(Socket socket) throws IOException {
 // Read the input stream, and return “200 OK”
 try {
  BufferedReader in = new BufferedReader(
   new InputStreamReader(socket.getInputStream()));
  log.info(in.readLine());

  OutputStream out = socket.getOutputStream();
  out.write(response.getBytes(StandardCharsets.UTF_8));
 } finally {
  socket.close();
 }
}

由于只有一個線程來處理請求,每個請求都必須等待前一個請求處理完成之后才能夠被響應。假設一個請求響應時間為100毫秒,那么這個服務器的每秒響應數(shù)(tps)只有10。

多線程

雖然handleRequest方法可能阻塞在IO上,但是CPU仍然可以處理更多的請求。但是在單線程情況下,這是無法做到的。因此,可以通過創(chuàng)建多線程的方式,來提升服務器的并行處理能力。

public static class HandleRequestRunnable implements Runnable {

 final Socket socket;

 public HandleRequestRunnable(Socket socket) {
  this.socket = socket;
 }

 public void run() {
  try {
   handleRequest(socket);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

ServerSocket listener = new ServerSocket(8080);
try {
 while (true) {
  Socket socket = listener.accept();
  new Thread(new HandleRequestRunnable(socket)).start();
 }
} finally {
 listener.close();
}

這里,accept()方法仍然在主線程中調(diào)用,但是一旦TCP連接建立之后,將會創(chuàng)建一個新的線程來處理新的請求,既在新的線程中執(zhí)行前文中的handleRequest方法。

通過創(chuàng)建新的線程,主線程可以繼續(xù)接受新的TCP連接,且這些信求可以并行的處理。這個方式稱為“每個請求一個線程(thread per request)”。當然,還有其他方式來提高處理性能,例如 NGINX 和 Node.js 使用的異步事件驅(qū)動模型,但是它們不使用線程池,因此不在本文的討論范圍。

在每個請求一個線程實現(xiàn)中,創(chuàng)建一個線程(和后續(xù)的銷毀)開銷是非常昂貴的,因為JVM和操作系統(tǒng)都需要分配資源。另外,上面的實現(xiàn)還有一個問題,即創(chuàng)建的線程數(shù)是不可控的,這將可能導致系統(tǒng)資源被迅速耗盡。

資源耗盡

每個線程都需要一定的棧內(nèi)存空間。在最近的64位JVM中, 默認的棧大小 是1024KB。如果服務器收到大量請求,或者handleRequest方法執(zhí)行很慢,服務器可能因為創(chuàng)建了大量線程而崩潰。例如有1000個并行的請求,創(chuàng)建出來的1000個線程需要使用1GB的JVM內(nèi)存作為線程??臻g。另外,每個線程代碼執(zhí)行過程中創(chuàng)建的對象,還可能會在堆上創(chuàng)建對象。這樣的情況惡化下去,將會超出JVM堆內(nèi)存,并產(chǎn)生大量的垃圾回收操作,最終引發(fā) 內(nèi)存溢出(OutOfMemoryErrors) 。

這些線程不僅僅會消耗內(nèi)存,它們還會使用其他有限的資源,例如文件句柄、數(shù)據(jù)庫連接等。不可控的創(chuàng)建線程,還可能引發(fā)其他類型的錯誤和崩潰。因此,避免資源耗盡的一個重要方式,就是避免不可控的數(shù)據(jù)結(jié)構(gòu)。

順便說下,由于線程棧大小引發(fā)的內(nèi)存問題,可以通過-Xss開關(guān)來調(diào)整棧大小??s小線程棧大小之后,可以減少每個線程的開銷,但是可能會引發(fā) 棧溢出(StackOverflowErrors) 。對于一般應用程序而言,默認的1024KB過于富裕,調(diào)小為256KB或者512KB可能更為合適。Java允許的最小值是160KB。

線程池

為了避免持續(xù)創(chuàng)建新線程,可以通過使用簡單的線程池來限定線程池的上限。線程池會管理所有線程,如果線程數(shù)還沒有達到上限,線程池會創(chuàng)建線程到上限,且盡可能復用空閑的線程。

ServerSocket listener = new ServerSocket(8080);
ExecutorService executor = Executors.newFixedThreadPool(4);
try {
 while (true) {
  Socket socket = listener.accept();
  executor.submit( new HandleRequestRunnable(socket) );
 }
} finally {
 listener.close();
}

在這個示例中,沒有直接創(chuàng)建線程,而是使用了ExecutorService。它將需要執(zhí)行的任務(需要實現(xiàn)Runnables接口)提交到線程池,使用線程池中的線程執(zhí)行代碼。示例中,使用線程數(shù)量為4的固定大小線程池來處理所有請求。這限制了處理請求的線程數(shù)量,也限制了資源的使用。

除了通過 newFixedThreadPool 方法創(chuàng)建固定大小線程池,Executors類還提供了 newCachedThreadPool 方法。復用線程池還是有可能導致不可控的線程數(shù),但是它會盡可能使用之前已經(jīng)創(chuàng)建的空閑線程。通常該類型線程池適合使用在不會被外部資源阻塞的短任務上。

工作隊列

使用了固定大小線程池之后,如果所有的線程都繁忙,再新來一個請求將會發(fā)生什么呢?ThreadPoolExecutor使用一個隊列來保存等待處理的請求,固定大小線程池默認使用無限制的鏈表。注意,這又可能引起資源耗盡問題,但只要線程處理的速度大于隊列增長的速度就不會發(fā)生。然后前面示例中,每個排隊的請求都會持有套接字,在一些操作系統(tǒng)中,這將會消耗文件句柄。由于操作系統(tǒng)會限制進程打開的文件句柄數(shù),因此最好限制下工作隊列的大小。

public static ExecutorService newBoundedFixedThreadPool(int nThreads, int capacity) {
 return new ThreadPoolExecutor(nThreads, nThreads,
   0L, TimeUnit.MILLISECONDS,
   new LinkedBlockingQueue<Runnable>(capacity),
   new ThreadPoolExecutor.DiscardPolicy());
}

public static void boundedThreadPoolServerSocket() throws IOException {
 ServerSocket listener = new ServerSocket(8080);
 ExecutorService executor = newBoundedFixedThreadPool(4, 16);
 try {
  while (true) {
   Socket socket = listener.accept();
   executor.submit( new HandleRequestRunnable(socket) );
  }
 } finally {
  listener.close();
 }
}

這里我們沒有直接使用Executors.newFixedThreadPool方法來創(chuàng)建線程池,而是自己構(gòu)建了ThreadPoolExecutor對象,并將工作隊列長度限制為16個元素。

如果所有的線程都繁忙,新的任務將會填充到隊列中,由于隊列限制了大小為16個元素,如果超過這個限制,就需要由構(gòu)造ThreadPoolExecutor對象時的最后一個參數(shù)來處理了。示例中,使用了 拋棄策略(DiscardPolicy) ,即當隊列到達上限時,將拋棄新來的任務。初次之外,還有 中止策略(AbortPolicy) 和 調(diào)用者執(zhí)行策略(CallerRunsPolicy) 。前者將拋出一個異常,而后者會再調(diào)用者線程中執(zhí)行任務。

對于Web應用來說,最優(yōu)的默認策略應該是拋棄或者中止策略,并返回一個錯誤給客戶端(如 HTTP 503 錯誤)。當然也可以通過增加工作隊列長度的方式,避免拋棄客戶端請求,但是用戶請求一般不愿意進行長時間的等待,且這樣會更多的消耗服務器資源。工作隊列的用途,不是無限制的響應客戶端請求,而是平滑突發(fā)暴增的請求。通常情況下,工作隊列應該是空的。

線程數(shù)調(diào)優(yōu)

前面的示例展示了如何創(chuàng)建和使用線程池,但是,使用線程池的核心問題在于應該使用多少線程。首先,我們要確保達到線程上限時,不會引起資源耗盡。這里的資源包括內(nèi)存(堆和棧)、打開文件句柄數(shù)量、TCP連接數(shù)、遠程數(shù)據(jù)庫連接數(shù)和其他有限的資源。特別的,如果線程任務是計算密集型的,CPU核心數(shù)量也是資源限制之一,一般情況下線程數(shù)量不要超過CPU核心數(shù)量。

由于線程數(shù)的選定依賴于應用程序的類型,可能需要經(jīng)過大量性能測試之后,才能得出最優(yōu)的結(jié)果。當然,也可以通過增加資源數(shù)的方式,來提升應用程序的性能。例如,修改JVM堆內(nèi)存大小,或者修改操作系統(tǒng)的文件句柄上限等。然后,這些調(diào)整最終還是會觸及理論上限。

利特爾法則

利特爾法則 描述了在穩(wěn)定系統(tǒng)中,三個變量之間的關(guān)系。

其中L表示平均請求數(shù)量,λ表示請求的頻率,W表示響應請求的平均時間。舉例來說,如果每秒請求數(shù)為10次,每個請求處理時間為1秒,那么在任何時刻都有10個請求正在被處理?;氐轿覀兊脑掝},就是需要使用10個線程來進行處理。如果單個請求的處理時間翻倍,那么處理的線程數(shù)也要翻倍,變成20個。

理解了處理時間對于請求處理效率的影響之后,我們會發(fā)現(xiàn),通常理論上限可能不是線程池大小的最佳值。線程池上限還需要參考任務處理時間。

假設JVM可以并行處理1000個任務,如果每個請求處理時間不超過30秒,那么在最壞情況下,每秒最多只能處理33.3個請求。然而,如果每個請求只需要500毫秒,那么應用程序每秒可以處理2000個請求。

拆分線程池

在微服務或者面向服務架構(gòu)(SOA)中,通常需要訪問多個后端服務。如果其中一個服務性能下降,可能會引起線程池線程耗盡,從而影響對其他服務的請求。

應對后端服務失效的有效辦法是隔離每個服務所使用的線程池。在這種模式下,仍然有一個分派的線程池,將任務分派到不同的后端請求線程池中。該線程池可能因為一個緩慢的后端而沒有負載,而將負擔轉(zhuǎn)移到了請求緩慢后端的線程池中。

另外,多線程池模式還需要避免死鎖問題。如果每個線程都阻塞在等待未被處理請求的結(jié)果上時,就會發(fā)生死鎖。因此,多線程池模式下,需要了解每個線程池執(zhí)行的任務和它們之間的依賴,這樣可以盡可能避免死鎖問題。

總結(jié)

即使沒有在應用程序中直接使用線程池,它們也很有可能在應用程序中被應用服務器或者框架間接使用。 Tomcat 、 JBoss 、 Undertow 、 Dropwizard 等框架,都提供了調(diào)優(yōu)線程池(servlet執(zhí)行使用的線程池)的選項。

希望本文能夠提升對線程池的了解,對大家學習有所幫助。

相關(guān)文章

  • 詳解Spring Boot Security工作流程

    詳解Spring Boot Security工作流程

    Spring Security,這是一種基于 Spring AOP 和 Servlet 。這篇文章主要介紹了Spring Boot Security的相關(guān)知識,需要的朋友可以參考下
    2019-04-04
  • Java中的事件處理機制詳解

    Java中的事件處理機制詳解

    這篇文章主要介紹了Java中的事件處理機制詳解,Java事件處理是采取"委派事件模型",當事件發(fā)生時,產(chǎn)生事件的對象,會把此"信息"傳遞給"事件的監(jiān)聽者"處理,這里所說的"信息"實際上就是java.awt.event事件類庫里某個類創(chuàng)建對象,把它稱為"事件的對象",需要的朋友可以參考下
    2023-10-10
  • 詳解Java?Map中三個冷門容器的使用

    詳解Java?Map中三個冷門容器的使用

    本篇文章主要講解下Map家族中3個相對冷門的容器,分別是WeakHashMap、EnumMap、IdentityHashMap,?想必大家在平時的工作中也很少用到,或者壓根不知道他們的特性以及適用場景,本篇文章就帶你一探究竟
    2022-12-12
  • Spring Cloud 中@FeignClient注解中的contextId屬性詳解

    Spring Cloud 中@FeignClient注解中的contextId屬性詳解

    這篇文章主要介紹了Spring Cloud 中@FeignClient注解中的contextId屬性詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java性能調(diào)優(yōu)概述

    Java性能調(diào)優(yōu)概述

    本文介紹了Java程序性能的主要表現(xiàn)點、衡量程序性能的主要指標、性能調(diào)優(yōu)的層次、基本調(diào)優(yōu)策略和手段等,具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler

    MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler

    這篇文章主要為大家介紹了MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 搭建Spring Boot聚合項目的實現(xiàn)示例

    搭建Spring Boot聚合項目的實現(xiàn)示例

    本文主要介紹了搭建Spring Boot聚合項目的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-04-04
  • Spring Boot集成Spring Cloud Security進行安全增強的方法

    Spring Boot集成Spring Cloud Security進行安全增強的方法

    Spring Cloud Security是Spring Security的擴展,它提供了對Spring Cloud體系中的服務認證和授權(quán)的支持,包括OAuth2、JWT等,這篇文章主要介紹了Spring Boot集成Spring Cloud Security進行安全增強,需要的朋友可以參考下
    2024-11-11
  • idea代碼模板設置方式

    idea代碼模板設置方式

    這篇文章主要介紹了idea代碼模板設置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 深入了解Java?Synchronized鎖升級過程

    深入了解Java?Synchronized鎖升級過程

    java中的鎖是針對對象而言的,它鎖住的是一個對象,并且具有可重入的性質(zhì),下面這篇文章主要給大家介紹了關(guān)于Java?Synchronized鎖升級過程的相關(guān)資料,需要的朋友可以參考下
    2022-03-03

最新評論

鄂托克旗| 晋州市| 湖南省| 达尔| 柳林县| 繁峙县| 盈江县| 遂宁市| 错那县| 谷城县| 文登市| 肇源县| 山丹县| 五原县| 通化县| 晋州市| 文昌市| 吴江市| 临洮县| 合山市| 陵川县| 新泰市| 碌曲县| 昌宁县| 淄博市| 平泉县| 贡觉县| 洪湖市| 赤峰市| 禹州市| 于田县| 神农架林区| 漠河县| 民权县| 东乡族自治县| 大渡口区| 阳朔县| 伊川县| 云阳县| 彝良县| 天水市|