日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java并发编程实战~Thread-Per-Message模式

發布時間:2024/7/23 java 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程实战~Thread-Per-Message模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們曾經把并發編程領域的問題總結為三個核心問題:分工、同步和互斥。其中,同步和互斥相關問題更多地源自微觀,而分工問題則是源自宏觀。我們解決問題,往往都是從宏觀入手,在編程領域,軟件的設計過程也是先從概要設計開始,而后才進行詳細設計。同樣,解決并發編程問題,首要問題也是解決宏觀的分工問題

并發編程領域里,解決分工問題也有一系列的設計模式,比較常用的主要有 Thread-Per-Message 模式、Worker Thread 模式、生產者 - 消費者模式等等。今天我們重點介紹 Thread-Per-Message 模式。

如何理解 Thread-Per-Message 模式

現實世界里,很多事情我們都需要委托他人辦理,一方面受限于我們的能力,總有很多搞不定的事,比如教育小朋友,搞不定怎么辦呢?只能委托學校老師了;另一方面受限于我們的時間,比如忙著寫 Bug,哪有時間買別墅呢?只能委托房產中介了。委托他人代辦有一個非常大的好處,那就是可以專心做自己的事了。

在編程領域也有很多類似的需求,比如寫一個 HTTP Server,很顯然只能在主線程中接收請求,而不能處理 HTTP 請求,因為如果在主線程中處理 HTTP 請求的話,那同一時間只能處理一個請求,太慢了!怎么辦呢?可以利用代辦的思路,創建一個子線程,委托子線程去處理 HTTP 請求。

這種委托他人辦理的方式,在并發編程領域被總結為一種設計模式,叫做 Thread-Per-Message 模式,簡言之就是為每個任務分配一個獨立的線程。這是一種最簡單的分工方法,實現起來也非常簡單。

用 Thread 實現 Thread-Per-Message 模式

Thread-Per-Message 模式的一個最經典的應用場景是網絡編程里服務端的實現,服務端為每個客戶端請求創建一個獨立的線程,當線程處理完請求后,自動銷毀,這是一種最簡單的并發處理網絡請求的方法。

網絡編程里最簡單的程序當數 echo 程序了,echo 程序的服務端會原封不動地將客戶端的請求發送回客戶端。例如,客戶端發送 TCP 請求"Hello World",那么服務端也會返回"Hello World"。

下面我們就以 echo 程序的服務端為例,介紹如何實現 Thread-Per-Message 模式。在 Java 語言中,實現 echo 程序的服務端還是很簡單的。只需要 30 行代碼就能夠實現,示例代碼如下,我們為每個請求都創建了一個 Java 線程,核心代碼是:new Thread(()->{…}).start()。

final ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8080)); //處理請求 try {while (true) {// 接收請求SocketChannel sc = ssc.accept();// 每個請求都創建一個線程new Thread(()->{try {// 讀SocketByteBuffer rb = ByteBuffer.allocateDirect(1024);sc.read(rb);//模擬處理請求Thread.sleep(2000);// 寫SocketByteBuffer wb = (ByteBuffer)rb.flip();sc.write(wb);// 關閉Socketsc.close();}catch(Exception e){throw new UncheckedIOException(e);}}).start();} } finally {ssc.close(); }

如果你熟悉網絡編程,相信你一定會提出一個很尖銳的問題:上面這個 echo 服務的實現方案是不具備可行性的。原因在于 Java 中的線程是一個重量級的對象,創建成本很高,一方面創建線程比較耗時,另一方面線程占用的內存也比較大。所以,為每個請求創建一個新的線程并不適合高并發場景。

于是,你開始質疑 Thread-Per-Message 模式,而且開始重新思索解決方案,這時候很可能你會想到 Java 提供的線程池。你的這個思路沒有問題,但是引入線程池難免會增加復雜度。其實你完全可以換一個角度來思考這個問題,語言、工具、框架本身應該是幫助我們更敏捷地實現方案的,而不是用來否定方案的,Thread-Per-Message 模式作為一種最簡單的分工方案,Java 語言支持不了,顯然是 Java 語言本身的問題。
Java 語言里,Java 線程是和操作系統線程一一對應的,這種做法本質上是將 Java 線程的調度權完全委托給操作系統,而操作系統在這方面非常成熟,所以這種做法的好處是穩定、可靠,但是也繼承了操作系統線程的缺點:創建成本高。為了解決這個缺點,Java 并發包里提供了線程池等工具類。這個思路在很長一段時間里都是很穩妥的方案,但是這個方案并不是唯一的方案。

業界還有另外一種方案,叫做輕量級線程。這個方案在 Java 領域知名度并不高,但是在其他編程語言里卻叫得很響,例如 Go 語言、Lua 語言里的協程,本質上就是一種輕量級的線程。輕量級的線程,創建的成本很低,基本上和創建一個普通對象的成本相似;并且創建的速度和內存占用相比操作系統線程至少有一個數量級的提升,所以基于輕量級線程實現 Thread-Per-Message 模式就完全沒有問題了。

Java 語言目前也已經意識到輕量級線程的重要性了,OpenJDK 有個 Loom 項目,就是要解決 Java 語言的輕量級線程問題,在這個項目中,輕量級線程被叫做 Fiber。下面我們就來看看基于 Fiber 如何實現 Thread-Per-Message 模式。

用 Fiber 實現 Thread-Per-Message 模式

Loom 項目在設計輕量級線程時,充分考量了當前 Java 線程的使用方式,采取的是盡量兼容的態度,所以使用上還是挺簡單的。用 Fiber 實現 echo 服務的示例代碼如下所示,對比 Thread 的實現,你會發現改動量非常小,只需要把 new Thread(()->{…}).start() 換成 Fiber.schedule(()->{}) 就可以了。

final ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8080)); //處理請求 try{while (true) {// 接收請求final SocketChannel sc = serverSocketChannel.accept();Fiber.schedule(()->{try {// 讀SocketByteBuffer rb = ByteBuffer.allocateDirect(1024);sc.read(rb);//模擬處理請求LockSupport.parkNanos(2000*1000000);// 寫SocketByteBuffer wb = (ByteBuffer)rb.flip()sc.write(wb);// 關閉Socketsc.close();} catch(Exception e){throw new UncheckedIOException(e);}});}//while }finally{ssc.close(); }

那使用 Fiber 實現的 echo 服務是否能夠達到預期的效果呢?我們可以在 Linux 環境下做一個簡單的實驗,步驟如下:

  • 首先通過 ulimit -u 512 將用戶能創建的最大進程數(包括線程)設置為 512;
  • 啟動 Fiber 實現的 echo 程序;
  • 利用壓測工具 ab 進行壓測:ab -r -c 20000 -n 200000 http:// 測試機 IP 地址:8080/

壓測執行結果如下:

Concurrency Level: 20000 Time taken for tests: 67.718 seconds Complete requests: 200000 Failed requests: 0 Write errors: 0 Non-2xx responses: 200000 Total transferred: 16400000 bytes HTML transferred: 0 bytes Requests per second: 2953.41 [#/sec] (mean) Time per request: 6771.844 [ms] (mean) Time per request: 0.339 [ms] (mean, across all concurrent requests) Transfer rate: 236.50 [Kbytes/sec] received Connection Times (ms)min mean[+/-sd] median max Connect: 0 557 3541.6 1 63127 Processing: 2000 2010 31.8 2003 2615 Waiting: 1986 2008 30.9 2002 2615 Total: 2000 2567 3543.9 2004 65293

你會發現即便在 20000 并發下,該程序依然能夠良好運行。同等條件下,Thread 實現的 echo 程序 512 并發都抗不過去,直接就 OOM 了。
如果你通過 Linux 命令 top -Hp pid 查看 Fiber 實現的 echo 程序的進程信息,你可以看到該進程僅僅創建了 16(不同 CPU 核數結果會不同)個操作系統線程。


如果你對 Loom 項目感興趣,也想上手試一把,可以下載源代碼自己構建,構建方法可以參考Project Loom 的相關資料,不過需要注意的是構建之前一定要把代碼分支切換到 Fibers。

總結

并發編程領域的分工問題,指的是如何高效地拆解任務并分配給線程。前面我們在并發工具類模塊中已經介紹了不少解決分工問題的工具類,例如 Future、CompletableFuture 、CompletionService、Fork/Join 計算框架等,這些工具類都能很好地解決特定應用場景的問題,所以,這些工具類曾經是 Java 語言引以為傲的。不過這些工具類都繼承了 Java 語言的老毛病:太復雜。

如果你一直從事 Java 開發,估計你已經習以為常了,習慣性地認為這個復雜度是正常的。不過這個世界時刻都在變化,曾經正常的復雜度,現在看來也許就已經沒有必要了,例如 Thread-Per-Message 模式如果使用線程池方案就會增加復雜度。

Thread-Per-Message 模式在 Java 領域并不是那么知名,根本原因在于 Java 語言里的線程是一個重量級的對象,為每一個任務創建一個線程成本太高,尤其是在高并發領域,基本就不具備可行性。不過這個背景條件目前正在發生巨變,Java 語言未來一定會提供輕量級線程,這樣基于輕量級線程實現 Thread-Per-Message 模式就是一個非常靠譜的選擇。
當然,對于一些并發度沒那么高的異步場景,例如定時任務,采用 Thread-Per-Message 模式是完全沒有問題的。實際工作中,我就見過完全基于 Thread-Per-Message 模式實現的分布式調度框架,這個框架為每個定時任務都分配了一個獨立的線程。

?

總結

以上是生活随笔為你收集整理的Java并发编程实战~Thread-Per-Message模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。