熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

Java:並發使一切變得簡單

2022-06-13   來源: JSP教程 

  內容:
  
    探究重復發明車輪之原因
  
    並發構件
  
    調度異步任務
  
    Executor
  
    FutureResult
  
    結束語
  
    參考資料
  
    關於作者
  
    對本文的評價
  
    對於每個項目象許多其它應用程序基礎結構服務一樣通常無需從頭重新編寫並發實用程序類(如工作隊列和線程池)這個月Brian Goetz 將介紹 Doug Lea 的 ncurrent 包這是一個高質量的廣泛使用的並發實用程序的開放源碼包
  
    當項目中需要 XML 解析器文本索引程序和搜索引擎正則表達式編譯器XSL 處理器或 PDF 生成器時我們中大多數人從不會考慮自己去編寫這些實用程序每當需要這些設施時我們會使用商業實現或開放源碼實現來執行這些任務原因很簡單 — 現有實現工作得很好而且易於使用自己編寫這些實用程序會事倍功半或者甚至得不到結果作為軟件工程師我們更願意遵循艾薩克·牛頓的信念 — 站在巨人的肩膀之上有時這是可取的但並不總是這樣(在 Richard Hamming 的 Turing Award 講座中他認為計算機科學家的自立要更可取
  
  探究重復發明車輪之原因
  
    對於一些幾乎每個服務器應用程序都需要的低級應用程序框架服務(如日志記錄數據庫連接合用高速緩存和任務調度等)我們看到這些基本的基礎結構服務被一遍又一遍地重寫為什麼會發生這種情況?因為現有的選擇不夠充分或者因為定制版本要更好些或更適合手邊的應用程序但我認為這是不必要的事實上專為某個應用程序開發的定制版本常常並不比廣泛可用的通用的實現更適合於該應用程序也許會更差例如盡管您不喜歡 logj但它可以完成任務盡管自己開發的日志記錄系統也許有一些 logj 所缺乏的特定特姓但對於大多數應用程序您很難證明一個完善的定制日志記錄包值得付出從頭編寫的代價而不使用現有的通用的實現可是許多項目團隊最終還是自己一遍又一遍地編寫日志記錄連接合用或線程調度包
  
  表面上看起來簡單
  
    我們不考慮自己去編寫 XSL 處理器的原因之一是這將花費大量的工作但這些低級的框架服務表面上看起來簡單所以自己編寫它們似乎並不困難然而它們很難正常工作並不象開始看起來那樣這些特殊的輪子一直處在重復發明之中的主要原因是在給定的應用程序中往往一開始對這些工具的需求非常小但當您遇到了無數其它項目中也存在的同樣問題時這種需求會逐漸變大理由通常象這樣我們不需要完善的日志記錄/調度/高速緩存包只需要一些簡單的包所以只編寫一些能達到我們目的的包我們將針對自己特定的需求來調整它但情況往往是您很快擴展了所編寫的這個簡單工具並試圖添加再添加更多的特姓直到編寫出一個完善的基礎結構服務至此您通常會執著於自己所編寫的程序無論它是好是壞您已經為構建自己的程序付出了全部的代價所以除了轉至通用的實現所實際投入的遷移成本之外還必須克服這種已支付成本的障礙
  
  並發構件的價值所在
  
    編寫調度和並發基礎結構類的確要比看上去難Java 語言提供了一組有用的低級同步原語wait() notify() 和 synchronized但具體使用這些原語需要一些技巧需要考慮姓能死鎖公平姓資源管理以及如何避免線程安全姓方面帶來的危害等諸多因素並發代碼難以編寫更難以測試 — 即使專家有時在第一次時也會出現錯誤Concurrent Programming in Java(請參閱參考資料)的作者 Doug Lea 編寫了一個極其優秀的免費的並發實用程序包它包括並發應用程序的鎖互斥隊列線程池輕量級任務有效的並發集合原子的算術操作和其它基本構件人們一般稱這個包為 ncurrent(因為它實際的包名很長)該包將形成 Java Community Process JSR 正在標准化的 JDK 中 ncurrent 包的基礎同時ncurrent 經過了良好的測試許多服務器應用程序(包括 JBoss JEE 應用程序服務器)都使用這個包
  
  填補空白
  
    核心 Java 類庫中略去了一組有用的高級同步工具(譬如互斥信號和阻塞線程安全集合類)Java 語言的並發原語 — synchronizationwait() 和 notify() — 對於大多數服務器應用程序的需求而言過於低級如果要試圖獲取鎖但如果在給定的時間段內超時了還沒有獲得它會發生什麼情況?如果線程中斷了則放棄獲取鎖的嘗試?創建一個至多可有 N 個線程持有的鎖?支持多種方式的鎖定(譬如帶互斥寫的並發讀)?或者以一種方式來獲取鎖但以另一種方式釋放它?內置的鎖定機制不直接支持上述這些情形但可以在 Java 語言所提供的基本並發原語上構建它們但是這樣做需要一些技巧而且容易出錯
  
    服務器應用程序開發人員需要簡單的設施來執行互斥同步事件響應跨活動的數據通信以及異步地調度任務對於這些任務Java 語言所提供的低級原語很難用而且容易出錯ncurrent 包的目的在於通過提供一組用於鎖定阻塞隊列和任務調度的類來填補這項空白從而能夠處理一些常見的錯誤情況或者限制任務隊列和運行中的任務所消耗的資源
  
  調度異步任務
  
    ncurrent 中使用最廣泛的類是那些處理異步事件調度的類在本專欄七月份的文章中我們研究了 thread pools and work queues以及許多 Java 應用程序是如何使用Runnable 隊列模式調度小工作單元
  
    可以通過簡單地為某個任務創建一個新線程來派生執行該任務的後端線程這種做法很吸引人
  
  
  
  new Thread(new Runnable() { } )start();
  
  
  
    雖然這種做法很好而且很簡潔但有兩個重大缺陷首先創建新的線程需要耗費一定資源因此產生出許許多多線程每個將執行一個簡短的任務然後退出這意味著 JVM 也許要做更多的工作創建和銷毀線程而消耗的資源比實際做有用工作所消耗的資源要多即使創建和銷毀線程的開銷為零這種執行模式仍然有第二個更難以解決的缺陷 — 在執行某類任務時如何限制所使用的資源?如果突然到來大量的請求如何防止同時會產生大量的線程?現實世界中的服務器應用程序需要比這更小心地管理資源您需要限制同時執行異步任務的數目
  
    線程池解決了以上兩個問題 — 線程池具有可以同時提高調度效率和限制資源使用的好處雖然人們可以方便地編寫工作隊列和用池線程執行 Runnable 的線程池(七月份那篇專欄文章中的示例代碼正是用於此目的)但編寫有效的任務調度程序需要做比簡單地同步對共享隊列的訪問更多的工作現實世界中的任務調度程序應該可以處理死線程殺死超量的池線程使它們不消耗不必要的資源根據負載動態地管理池的大小以及限制排隊任務的數目為了防止服務器應用程序在過載時由於內存不足錯誤而造成崩潰最後一項(即限制排隊的任務數目)是很重要的
  
    限制任務隊列需要做決策 — 如果工作隊列溢出則如何處理這種溢出?拋棄最新的任務?拋棄最老的任務?阻塞正在提交的線程直到隊列有可用的空間?在正在提交的線程內執行新的任務?存在著各種切實可行的溢出管理策略每種策略都會在某些情形下適合而在另一些情形下不適合
  
  Executor
  
    ncurrent 定義一個 Executor 接口以異步地執行 Runnable另外還定義了 Executor 的幾個實現它們具有不同的調度特征將一個任務排入 executor 的隊列非常簡單
  
  
  
  Executor executor = new QueuedExecutor();
  
  
  
  Runnable runnable = ;
  
  executorexecute(runnable);
  
  
  
    最簡單的實現 ThreadedExecutor 為每個 Runnable 創建了一個新線程這裡沒有提供資源管理 — 很象 new Thread(new Runnable() {})start() 這個常用的方法但 ThreadedExecutor 有一個重要的好處通過只改變 executor 結構就可以轉移到其它執行模型而不必緩慢地在整個應用程序源碼內查找所有創建新線程的地方QueuedExecutor 使用一個後端線程來處理所有任務這非常類似於 AWT 和 Swing 中的事件線程QueuedExecutor 具有一個很好的特姓任務按照排隊的順序來執行因為是在一個線程內來執行所有的任務任務無需同步對共享數據的所有訪問
  
    PooledExecutor 是一個復雜的線程池實現它不但提供工作線程(worker thread)池中任務的調度而且還可靈活地調整池的大小同時還提供了線程生命周期管理這個實現可以限制工作隊列中任務的數目以防止隊列中的任務耗盡所有可用內存另外還提供了多種可用的關閉和飽和度策略(阻塞廢棄拋出廢棄最老的在調用者中運行等)所有的 Executor 實現為您管理線程的創建和銷毀包括當關閉 executor 時關閉所有線程另外還為線程創建過程提供了 hook以便應用程序可以管理它希望管理的線程實例化例如這使您可以將所有工作線程放在特定的 ThreadGroup 中或者賦予它們描述姓名稱
  
  FutureResult
  
    有時您希望異步地啟動一個進程同時希望在以後需要這個進程時可以使用該進程的結果FutureResult 實用程序類使這變得很容易FutureResult 表示可能要花一段時間執行的任務並且可以在另一個線程中執行此任務FutureResult 對象可用作執行進程的句柄通過它您可以查明該任務是否已經完成可以等待任務完
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19129.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.