The j.u.c Synchronizer Framework翻譯(一)背景與需求
摘要
在J2SE 1.5的java.util.concurrent包(下稱j.u.c包)中,大部分的同步器(例如鎖,屏障等等)都是基於 AbstractQueuedSynchronizer(下稱AQS類)這個簡單的框架來構建的。這個框架為同步狀態的原子性管理、線程的阻塞和解除阻塞 以及排隊提供了一種通用機製。這篇論文主要描述了這個框架基本原理、設計、實現、用法以及性能。
1. 背景介紹
通過JCP的JSR166規範,Java的1.5版本引入了j.u.c包,這個包提供了一係列支持中等程度並發的類。這些組件是一係列的同步器(抽象數據 類型(ADT))。這些同步器主要維護著以下幾個功能:內部同步狀態的管理(例如:表示一個鎖的狀態是獲取還是釋放),同步狀態的更新和檢查操作,且至少 有一個方法會導致調用線程在同步狀態被獲取時阻塞,以及在其他線程改變這個同步狀態時解除線程的阻塞。上述的這些的實際例子包括:互斥排它鎖的不同形式、 讀寫鎖、信號量、屏障、Future、事件指示器以及傳送隊列等。
幾乎任一同步器都可以用來實現其他形式的同步器。例如,可以用可重入鎖實現信號量或者用信號量實現可重入鎖。但是,這樣做帶來的複雜性,開銷,不靈活使其 至多隻能是個二流工程。且缺乏吸引力。如果任何這樣的構造方式不能在本質上比其他形式更簡潔,那麼開發者就不應該隨意地選擇其中的某個來構建另一個同步 器。取而代之,JSR166建立了一個小框架,AQS類。這個框架為構造同步器提供一種通用的機製,並且被j.u.c包中大部分類使用,同時很多用戶也用 它來定義自己的同步器。
在這篇論文的下麵部分會討論這個框架的需求、設計與實現背後的主要思路、示例用法,以及性能指標的一些測量。
2 需求
2.1 功能
同步器一般包含兩種方法,一種是acquire,另一種是release。acquire操作阻塞調用的線程,直到或除非同步狀態允許其繼續執行。而release操作則是通過某種方式改變同步狀態,使得一或多個被acquire阻塞的線程繼續執行。
j.u.c包中並沒有對同步器的API做一個統一的定義。因此,有一些類定義了通用的接口(如Lock),而另外一些則定義了其專有的版本。因此在不同的 類中,acquire和release操作的名字和形式會各有不同。例 如:Lock.lock,Semaphore.acquire,CountDownLatch.await和FutureTask.get,在這個框架 裏,這些方法都是acquire操作。但是,J.U.C為支持一係列常見的使用選項,在類間都有個一致約定。在有意義的情況下,每一個同步器都支持下麵的 操作:
- 阻塞和非阻塞(例如tryLock)同步。
- 可選的超時設置,讓調用者可以放棄等待
- 通過中斷實現的任務取消,通常是分為兩個版本,一個acquire可取消,而另一個不可以。
同步器的實現根據其狀態是否獨占而有所不同。獨占狀態的同步器,在同一時間隻有一個線程可以通過阻塞點,而共享狀態的同步器可以同時有多個線程在執行。一 般鎖的實現類往往隻維護獨占狀態,但是,例如計數信號量在數量許可的情況下,允許多個線程同時執行。為了使框架能得到廣泛應用,這兩種模式都要支持。
j.u.c包裏還定義了Condition接口,用於支持管程形式的await/signal操作,這些操作與獨占模式的Lock類有關,且Condition的實現天生就和與其關聯的Lock類緊密相關。
2.2 性能目標
Java內置鎖(使用synchronized的方法或代碼塊)的性能問題一直以來都在被人們關注,並且已經有一係列的文章描述其構造(例如引文[1], [3])。然而,大部分的研究主要關注的是在單核處理器上大部分時候使用於單線程上下文環境中時,如何盡量降低其空間(因為任何的Java對象都可以當成 是鎖)和時間的開銷。對於同步器來說這些都不是特別重要:程序員僅在需要的時候才會使用同步器,因此並不需要壓縮空間來避免浪費,並且同步器幾乎是專門用 在多線程設計中(特別是在多核處理器上),在這種環境下,偶爾的競爭是在意料之中的。因此,常規的JVM鎖優化策略主要是針對零競爭的場景,而其它場景則 使用缺乏可預見性的“慢速路徑(slow paths)” ,所以常規的JVM鎖優化策略並不適用於嚴重依賴於J.U.C包的典型多線程服務端應用。
這裏主要的性能目標是可伸縮性,即在大部分情況下,即使,或特別在同步器有競爭的情況下,穩定地保證其效率。在理想的情況下,不管有多少線程正試圖通過同 步點,通過同步點的開銷都應該是個常量。在某一線程被允許通過同步點但還沒有通過的情況下,使其耗費的總時間最少,這是主要目標之一。然而,這也必須考慮 平衡各種資源,包括總CPU時間的需求,內存負載以及線程調度的開銷。例如:獲取自旋鎖通常比阻塞鎖所需的時間更短,但是通常也會浪費CPU時鍾周期,並 且造成內存競爭,所以使用的並不頻繁。
實現同步器的這些目標包含了兩種不同的使用類型。大部分應用程序是最大化其總的吞吐量,容錯性,並且最好保證盡量減少饑餓的情況。然而,對於那些控製資源 分配的程序來說,更重要是去維持多線程讀取的公平性,可以接受較差的總吞吐量。沒有任何框架可以代表用戶去決定應該選擇哪一個方式,因此,應該提供不同的 公平策略。
無論同步器的內部實現是多麼的精雕細琢,它還是會在某些應用中產生性能瓶頸。因此,框架必須提供相應的監視工具讓用戶發現和緩和這些瓶頸。至少需要提供一種方式來確定有多少線程被阻塞了。
文章轉自 並發編程網-ifeve.com
最後更新:2017-05-23 11:02:38
上一篇:
《C#並發編程經典實例》—— 發送通知給上下文
下一篇:
《C#並發編程經典實例》—— 用限流和抽樣抑製事件流
遊戲安全資訊精選 2017年第十期 英國彩票網遭遇DDoS攻擊,中斷90分鍾 DNSMASQ多高危漏洞公告 阿裏雲協助警方破獲國內最大黑客攻擊案,攻擊峰值690G
maven編譯時出現讀取XXX時出錯invalid LOC header (bad signature)
ubuntu/centos Server 安裝完成後,開啟SSH,配置IP,DNS
手機衛士08-獲取手機聯係人
stl-變異算法
Argo.AI CEO 撰文自述:DARPA 挑戰賽十年,自動駕駛還麵臨哪些挑戰?
HDU 3977 求斐波那契循環節
挑戰淘寶:且看如何用1500行搞定淘寶20000行Java SDK(1)
android 將圖片內容解析成字節數組,將字節數組轉換為ImageView可調用的Bitmap對象,圖片縮放,把字節數組保存為一個文件,把Bitmap轉Byte
Valve創始人再噴Win 8:它傷害了PC行業的每一個人