亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

單線(xiàn)程

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

為了能夠理解上述流程,我們不直接使用任何應(yīng)用服務(wù)器,而是從零開(kāi)始構(gòu)建一個(gè)簡(jiǎn)單的Web服務(wù)。該服務(wù)是大部分應(yīng)用服務(wù)器的縮影。一個(gè)簡(jiǎn)單的單線(xiàn)程Web服務(wù)大概是這樣的:

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)建了一個(gè) 服務(wù)端套接字(ServerSocket) ,監(jiān)聽(tīng)8080端口,然后循環(huán)檢查這個(gè)套接字,查看是否有新的連接。一旦有新的連接被接受,這個(gè)套接字會(huì)被傳入handleRequest方法。這個(gè)方法會(huì)將數(shù)據(jù)流解析成HTTP請(qǐng)求,進(jìn)行響應(yīng),并寫(xiě)入響應(yīng)數(shù)據(jù)。在這個(gè)簡(jiǎn)單的示例中,handleRequest方法僅僅實(shí)現(xiàn)數(shù)據(jù)流的讀入,返回一個(gè)簡(jiǎn)單的響應(yīng)數(shù)據(jù)。在通常實(shí)現(xiàn)中,該方法還會(huì)復(fù)雜的多,比如從數(shù)據(jù)庫(kù)讀取數(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();
 }
}

由于只有一個(gè)線(xiàn)程來(lái)處理請(qǐng)求,每個(gè)請(qǐng)求都必須等待前一個(gè)請(qǐng)求處理完成之后才能夠被響應(yīng)。假設(shè)一個(gè)請(qǐng)求響應(yīng)時(shí)間為100毫秒,那么這個(gè)服務(wù)器的每秒響應(yīng)數(shù)(tps)只有10。

多線(xiàn)程

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

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()方法仍然在主線(xiàn)程中調(diào)用,但是一旦TCP連接建立之后,將會(huì)創(chuàng)建一個(gè)新的線(xiàn)程來(lái)處理新的請(qǐng)求,既在新的線(xiàn)程中執(zhí)行前文中的handleRequest方法。

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

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

資源耗盡

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

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

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

線(xiàn)程池

為了避免持續(xù)創(chuàng)建新線(xiàn)程,可以通過(guò)使用簡(jiǎn)單的線(xiàn)程池來(lái)限定線(xiàn)程池的上限。線(xiàn)程池會(huì)管理所有線(xiàn)程,如果線(xiàn)程數(shù)還沒(méi)有達(dá)到上限,線(xiàn)程池會(huì)創(chuàng)建線(xiàn)程到上限,且盡可能復(fù)用空閑的線(xiàn)程。

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();
}

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

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

工作隊(duì)列

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

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();
 }
}

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

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

對(duì)于Web應(yīng)用來(lái)說(shuō),最優(yōu)的默認(rèn)策略應(yīng)該是拋棄或者中止策略,并返回一個(gè)錯(cuò)誤給客戶(hù)端(如 HTTP 503 錯(cuò)誤)。當(dāng)然也可以通過(guò)增加工作隊(duì)列長(zhǎng)度的方式,避免拋棄客戶(hù)端請(qǐng)求,但是用戶(hù)請(qǐng)求一般不愿意進(jìn)行長(zhǎng)時(shí)間的等待,且這樣會(huì)更多的消耗服務(wù)器資源。工作隊(duì)列的用途,不是無(wú)限制的響應(yīng)客戶(hù)端請(qǐng)求,而是平滑突發(fā)暴增的請(qǐng)求。通常情況下,工作隊(duì)列應(yīng)該是空的。

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

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

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

利特爾法則

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

其中L表示平均請(qǐng)求數(shù)量,λ表示請(qǐng)求的頻率,W表示響應(yīng)請(qǐng)求的平均時(shí)間。舉例來(lái)說(shuō),如果每秒請(qǐng)求數(shù)為10次,每個(gè)請(qǐng)求處理時(shí)間為1秒,那么在任何時(shí)刻都有10個(gè)請(qǐng)求正在被處理?;氐轿覀兊脑?huà)題,就是需要使用10個(gè)線(xiàn)程來(lái)進(jìn)行處理。如果單個(gè)請(qǐng)求的處理時(shí)間翻倍,那么處理的線(xiàn)程數(shù)也要翻倍,變成20個(gè)。

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

假設(shè)JVM可以并行處理1000個(gè)任務(wù),如果每個(gè)請(qǐng)求處理時(shí)間不超過(guò)30秒,那么在最壞情況下,每秒最多只能處理33.3個(gè)請(qǐng)求。然而,如果每個(gè)請(qǐng)求只需要500毫秒,那么應(yīng)用程序每秒可以處理2000個(gè)請(qǐng)求。

拆分線(xiàn)程池

在微服務(wù)或者面向服務(wù)架構(gòu)(SOA)中,通常需要訪(fǎng)問(wèn)多個(gè)后端服務(wù)。如果其中一個(gè)服務(wù)性能下降,可能會(huì)引起線(xiàn)程池線(xiàn)程耗盡,從而影響對(duì)其他服務(wù)的請(qǐng)求。

應(yīng)對(duì)后端服務(wù)失效的有效辦法是隔離每個(gè)服務(wù)所使用的線(xiàn)程池。在這種模式下,仍然有一個(gè)分派的線(xiàn)程池,將任務(wù)分派到不同的后端請(qǐng)求線(xiàn)程池中。該線(xiàn)程池可能因?yàn)橐粋€(gè)緩慢的后端而沒(méi)有負(fù)載,而將負(fù)擔(dān)轉(zhuǎn)移到了請(qǐng)求緩慢后端的線(xiàn)程池中。

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

總結(jié)

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

希望本文能夠提升對(duì)線(xiàn)程池的了解,對(duì)大家學(xué)習(xí)有所幫助。

相關(guān)文章

  • Java使用MyBatis框架分頁(yè)的5種方式

    Java使用MyBatis框架分頁(yè)的5種方式

    這篇文章主要為大家詳細(xì)介紹了Java使用MyBatis框架分頁(yè)的5種方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • 解決springboot讀取application.properties中文亂碼問(wèn)題

    解決springboot讀取application.properties中文亂碼問(wèn)題

    初用properties,讀取java properties文件的時(shí)候如果value是中文,會(huì)出現(xiàn)亂碼的問(wèn)題,所以本文小編將給大家介紹如何解決springboot讀取application.properties中文亂碼問(wèn)題,需要的朋友可以參考下
    2023-11-11
  • springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程分析

    springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程分析

    這篇文章主要介紹了springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程,本次使用jar包啟動(dòng)后端,故而準(zhǔn)備打包后的jar文件,需要的朋友可以參考下
    2022-12-12
  • Java使用Gateway自定義負(fù)載均衡過(guò)濾器

    Java使用Gateway自定義負(fù)載均衡過(guò)濾器

    這篇文章主要介紹了Java使用Gateway自定義負(fù)載均衡過(guò)濾器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 淺談將子類(lèi)對(duì)象賦值給父類(lèi)對(duì)象

    淺談將子類(lèi)對(duì)象賦值給父類(lèi)對(duì)象

    淺談將子類(lèi)對(duì)象賦值給父類(lèi)對(duì)象...
    2006-12-12
  • Java關(guān)于IO流的全面介紹

    Java關(guān)于IO流的全面介紹

    下面小編就為大家?guī)?lái)一篇Java關(guān)于IO流的全面介紹。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-08-08
  • jenkins安裝及其配置筆記

    jenkins安裝及其配置筆記

    這篇文章主要介紹了jenkins安裝及其配置筆記,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • JavaSE實(shí)現(xiàn)猜拳游戲

    JavaSE實(shí)現(xiàn)猜拳游戲

    這篇文章主要為大家詳細(xì)介紹了JavaSE實(shí)現(xiàn)猜拳游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • SpringBoot項(xiàng)目中同時(shí)操作多個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法

    SpringBoot項(xiàng)目中同時(shí)操作多個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法

    在實(shí)際項(xiàng)目開(kāi)發(fā)中可能存在需要同時(shí)操作兩個(gè)數(shù)據(jù)庫(kù)的場(chǎng)景,本文主要介紹了SpringBoot項(xiàng)目中同時(shí)操作多個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java快速生成PDF文檔的實(shí)例代碼

    Java快速生成PDF文檔的實(shí)例代碼

    在如今數(shù)字化時(shí)代,越來(lái)越多的人使用PDF文檔進(jìn)行信息傳遞和共享,而使用Java生成PDF文檔也成為了一個(gè)非常重要的技能,所以本文我們將為您介紹如何使用Java快速生成PDF文檔,需要的朋友可以參考下
    2023-09-09

最新評(píng)論