Java并發(fā)應用之任務執(zhí)行分析
概述
DougLea等人寫的《Java并發(fā)編程實戰(zhàn)》中是這樣描述任務的:“在多數(shù)的并發(fā)應用程序中,都是圍繞著“任務執(zhí)行”來構造的,而任務通常是一些抽象且離散的工作單元,通過把應用程序的工作分解到多個任務中,可以簡化應用程序的組織結構,提供一種自然的事務邊界來優(yōu)化錯誤恢復過程,以及提供一種自然的并行工作結構來提升并發(fā)性。”這段話理解起來就是:我們將一個很復雜的工作A,分解成很多的小任務,然后讓這些小任務同時開始干自己的事情。當這些小任務都干完了后再合并成我們要完成的最終的那個復雜工作A。而如何合理的將這個復雜的任務A合理的拆解成一個個的小任務,以及如何安排執(zhí)行這些任務,最終高效的得到正確的結果成為我們要考慮的重要問題
1.任務執(zhí)行邊界劃分
當我們要完成一個復雜的任務時,或者是一個耗時很長的任務時,我們往往會將其劃分為多個任務并發(fā)的去執(zhí)行。在最理想的情況下,我們劃分的這些任務之間是相互獨立的:即劃分的任務之間不相互依賴,一個任務的執(zhí)行不依賴其他任務的狀態(tài),計算結果。任務之間的獨立性有助于實現(xiàn)并發(fā)。如果有足夠的處理器資源,那么獨立的任務可以并行執(zhí)行。
在正常的負載下,我們的應用程序應該要表現(xiàn)出良好的吞吐量和快速響應性。應用的提供商都希望程序能夠盡可能的支持更多的用戶,降低每個用戶的服務成本,而用戶則希望獲得盡快的程序響應。而且,當負荷過載時,應用程序的性能應該是逐漸緩和的降低,而不是直接以下就異常閃退。要實現(xiàn)這個目標就需要我們能劃分出清晰的任務邊界以及明確的任務執(zhí)行策略。如下圖所示:
理想情況下,劃分的三個子任務是獨立的,互相不依賴的,這樣我們就可以讓三個子任務同時進行運算,這樣復雜任務A的運算就會更快。用戶也會很快的得到響應。
2.服務器應用程序的想法和實現(xiàn)
在多數(shù)的服務器應用程序中都提供了一種自然的任務邊界劃分方式,每個用戶的一次請求為一個邊界,即一次請求對應一個獨立的任務。Web服務器,郵件服務器,文件服務器,EJB容器以及數(shù)據(jù)庫服務器等,這些服務器都能通過網(wǎng)絡接受遠程客戶的連接請求。每一次請求我們都把其作為一個任務處理,很明顯,這些任務之間是獨立的,比如,向郵件服務器提交一個消息后得到的結果,并不會受其他正在處理的消息的影響。
2.1 串行執(zhí)行任務
在應用程序的開發(fā)過程中,我們可以通過多種策略來調度任務,最簡單的方法就是在單個線程中串行執(zhí)行各項任務,也就是說任務必須一個個執(zhí)行,同一時間內,在該線程中只有一個任務在執(zhí)行,偽代碼如下:
public class WebServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(80); while (true){ Socket connection = serverSocket.accept(); handleRequest(connection);// 處理請求 } } }
上面的代碼是無法商用的,因為它每次都只能處理一個請求,其他的請求到來時,假設服務器正在處理請求,那么就得等待直到服務器處理完上一個請求,假設每一個請求的處理都超級快,那么這種辦法是可行的,但是現(xiàn)實世界中,服務器的情況是千變萬化的。所以這種方式是非常不推薦的。
而且更不可接受的是,假設用戶的Web請求中包含了一組不同的運算與IO操作。服務器必須要等待IO和運算完成。這些操作可能會由于網(wǎng)絡擁塞或者聯(lián)通性的問題而被阻塞,在單線程的程序中,阻塞不僅會推遲當前請求完成的時間,而且還將徹底阻止等待中的請求被處理,如果請求阻塞的時間過長,用戶會認為服務器是不可用的。
2.2 為每個請求創(chuàng)建線程來執(zhí)行任務
上一節(jié)我們說到單線程去執(zhí)行用戶請求是很不科學的,所以我們假設通過為每個請求創(chuàng)建一個線程去執(zhí)行它,會有問題嗎?帶著這個問題,我們一起來分析下:偽代碼如下所示:
public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(80); while (true){ Socket connection = serverSocket.accept(); Runnable task = new Runnable() { @Override public void run() { handleRequest(connection);// 處理請求 } }; new Thread(task).start(); } }
如上面的代碼所示,每一個請求我們都創(chuàng)建了一個線程去處理,任務的處理過程從主線程中分離出來,使得主循環(huán)能夠更快的等待下一個到來的連接。這使得程序在完成前面的請求之前可以提前接受新的請求,提高了程序的響應性。而且多個任務可以并行處理,也就是多個請求可以同時被處理,如果有多個處理器,或者是任務由于某種原因被阻塞,例如等待I/O完成,獲取鎖或者資源可用性等,程序的吞吐量將會得到提高,需要注意的是多個線程并行處理的時候,需要注意線程的安全性。
假設在正常的負載下,為每個請求任務分配一個線程的方法能提升程序串行執(zhí)行的性能,只要是請求到達速率不超出服務器的請求處理能力,那么這種方法可以同時帶來更快的響應性和更高的吞吐率。
那么在生產環(huán)境中,為每個任務分配一個線程這種方法就一定好嗎?當然不是,尤其是當我們要創(chuàng)建大量的線程時,主要的缺陷有以下幾點:
(1)線程生命周期的開銷非常高
線程的創(chuàng)建和銷毀并不是沒有代價的,根據(jù)平臺的不同,實際上的開銷也會有所不同,線程的創(chuàng)建和銷毀都需要時間
(2)線程的創(chuàng)建需要消耗資源
活躍的線程會消耗資源,特別是內存。如果可運行的線程數(shù)量多余可用處理器的數(shù)量沒那么有些線程將無事可干,而白白浪費系統(tǒng)資 源。給垃圾回收器帶來壓力。相反,如果已經擁有了足夠多的線程,使所有的CPU都處于忙碌狀態(tài),那么再創(chuàng)建線程反而會降低性能。
(3)穩(wěn)定性
在可創(chuàng)建的線程數(shù)量上存在一個限制。這個限制值將隨平臺的不同而不同,并且受多個因素制約,包括JVM的啟動參數(shù),Thread構造函數(shù)中請求棧的大小,以及底層操作系統(tǒng)對線程的限制等。如果破壞了這些限制,那么很可能會拋出OutOfMemoryError異常
所以綜上所述,在一定的范圍內,增加線程可以提高系統(tǒng)的吞吐率,但是如果超出了這個范圍,再創(chuàng)建更多的線程指揮降低程序的執(zhí)行速率,并且過多的創(chuàng)建一個線程,那么整個應用程序都將會崩潰,想要避免這種危險就應該對應用程序可以創(chuàng)建的線程數(shù)量進行限制。從而確保在線程達到限制時,程序也不會耗盡資源
到此這篇關于Java并發(fā)應用之任務執(zhí)行分析的文章就介紹到這了,更多相關Java任務執(zhí)行分析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatis?mapper.xml?注釋帶參數(shù)的坑及解決
這篇文章主要介紹了mybatis?mapper.xml?注釋帶參數(shù)的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01SpringBoot配置Spring Native的詳細步驟
配置 Spring Native 以減少 Spring Boot 應用的啟動時間,涉及幾個關鍵步驟,包括設置相應的依賴、配置文件以及構建過程,本文給大家就介紹了詳細的步驟和配置示例,需要的朋友可以參考下2024-11-11Spring Boot中使用Spring-data-jpa實現(xiàn)數(shù)據(jù)庫增刪查改
本篇文章主要介紹了Spring Boot中使用Spring-data-jpa實現(xiàn)增刪查改,非常具有實用價值,需要的朋友可以參考下。2017-03-03Springboot?如何使用BindingResult校驗參數(shù)
這篇文章主要介紹了Springboot?如何使用BindingResult校驗參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01SpringBoot整合EasyExcel實現(xiàn)導入導出數(shù)據(jù)
這篇文章主要為大家詳細介紹了如何使用Vue、SpringBoot和EasyExcel實現(xiàn)導入導出數(shù)據(jù)功能,感興趣的小伙伴可以跟隨小編一起學習一下2022-05-05SpringBoot+EasyPoi實現(xiàn)excel導出功能
最新小編遇到這樣一個需求,根據(jù)檢索條件查詢列表并將結果導出到excel,實現(xiàn)過程也非常簡單,感興趣的朋友跟隨小編一起看看吧2021-09-09