Java 語言的並發編程
就其自身來說並發編程是一種技術提供了操作的同時執行不論是在單一系統上還是分布在大量系統上這類操作實際是一些指令順序例如單獨某個頂級任務的子任務這類操作能夠並行執行或者是作為線程或者是作為進程線程和進程之間的本質區別在於進程通常是獨立的(例如獨立的地址空間)所以只能通過系統提供的進程間通信機制進行交互而線程通常共享單一進程的狀態信息能夠直接共享系統資源和內存中的對象
可以使用下面兩種方法之一通過多個進程來實現並發第一種方法是在同一個處理器上運行進程由操作系統處理進程之間的上下文環境切換(可以理解這種切換要比同一進程內多線程之間的上下文環境切換更慢)第二種方法是構建大規模的並行和復雜的分布式系統在不同的物理處理器上運行多個進程
從內建支持的角度來說Java 語言通過線程提供並發編程;每個 JVM 都能支持許多線程同時執行可以用以下兩種方法之一在 Java 語言中創建線程
繼承 javalangThread 類在這種情況下已經重寫的子類的 run() 方法必須包含實現線程運行時行為的代碼要執行這個代碼需要實例化子類對象然後調用對象的 start() 方法這樣就可以在內部執行 run() 方法了
創建 Runnable 接口的定制實現這個接口只包含一個 run() 方法在這個方法中要放置應用程序代碼要執行這個代碼需要實例化實現類的對象然後在創建新 Thread 時把對象作為構造函數的參數傳入然後調用新創建的線程對象的 start() 方法開始執行控制的新線程
線程安全性和同步
如果 Java 對象中的某個方法能夠安全地運行在多線程環境中那麼就稱該方法是 線程安全的要獲得這種安全性必須有一種機制通過該機制運行同一方法的多個線程就能夠同步其操作這樣在訪問相同的對象或代碼行時就會只允許一個線程被處理這種同步要求線程使用叫作 信號 的對象彼此進行溝通
有一種類型的信號叫作 互斥信號 或 互斥體顧名思義這個信號對象的擁有權是互斥的也就是說在任意指定時間只有一個線程能夠擁有互斥體其他想獲得所有權的線程會被阻塞它們必須等待直到擁有互斥體的線程釋放互斥體如果多個線程按順序排隊等候同一互斥體那麼在當前擁有者釋放它的時候只有一個等候線程能夠得到它;其他線程將繼續阻塞
在 年代初CAR Hoare 和其他人共同開發了一個叫作 監視器 的概念一個 監視器 就是一個代碼主體它的訪問受到互斥體的保護任何想執行這個代碼的線程都必須在代碼塊頂部得到關聯的互斥體然後在底部再釋放它因為在指定時間只有一個線程能夠擁有互斥體所以這就有效地保證了只有擁有它的線程才能執行監視器的代碼塊(受保護的代碼不需要相鄰 —— 例如Java 語言中的每個對象都有一個與之關聯的監視器)
任何想在 Java 語言中進行線程編程的開發人員都會立即把上面的內容當成 synchronized 關鍵字所帶來的效果可以確保包含在 synchronized 塊中的 Java 代碼在指定時間只被一個線程執行在內部可以由運行時將 synchronized 關鍵字轉換成某一種情況所有的競爭線程都試圖獲得與它們(指線程)正在操作的對象實例關聯的那個(惟一的一個)互斥體成功得到互斥體的線程將運行代碼然後在退出 synchronized 塊時釋放互斥體
等候和通知
wait/notify 構造在 Java 語言的線程間通信機制中也扮演了重要的角色基本的想法是一個線程需要的某個條件可以由另外一個線程促成這樣條件的 wait 就可以得到滿足一旦條件為真那麼引發條件的線程就會 notify 等候線程蘇醒並從中止的地方繼續進行
wait/notify 機制要比 synchronized 機制更難理解和判斷要想判斷出使用 wait/notify 的方法的行為邏輯就要求判斷出使用它的所有方法的邏輯一次判斷一個方法把該方法和其他方法隔離開是對整體系統行為得出錯誤結論的可靠方式顯然這樣做的復雜性會隨著要判斷的方法的數量增長而迅速提高
線程狀態
我前面提到過必須調用新創建的線程的 start() 方法來啟動它的執行但是僅僅是調用 start() 方法並不意味著線程會立即開始運行這個方法只是把線程的狀態從 new 變成 runnable只有在操作系統真正安排線程執行的時候線程狀態才會變成 running (從 runnable)
典型的操作系統支持兩種線程模型 —— 協作式和搶占式在協作式 模型中每個線程對於自己對 CPU 的控制權要保留多久什麼時候放棄有最終意見在這個模型中因為可能存在某個無賴線程占住控制權不放所以其他線程可能永遠無法得到運行在 搶占式 模型中操作系統本身采用基於時鐘滴答的計時器基於這個計時器操作系統可以強制把控制權從一個線程轉移到另外一個線程在這種情況下決定哪個線程會得到下一次控制權的調度策略就有可能基於各種指標例如相對優先級某個線程已經等待執行的時間長短等等
如果出於某些原因處在 running 狀態的線程需要等候某個資源(例如等候設備的輸入數據到達或者等候某些條件已經設定的通知)或者在試圖獲得互斥體的時候被阻塞因此線程決定睡眠那麼這時它可以進入 blocked 狀態當睡眠周期到期預期輸入到達或者互斥體當前的擁有者將其釋放並通知等候線程可以再次奪取互斥體時阻塞的線程重新進入 runnable 狀態
當線程的 run() 方法完成時(或者正常返回或者拋出 RuntimeException 這樣的未檢測到異常)線程將終止這時線程的狀態是 dead當線程死亡時就不能通過再次調用它的 start() 方法來重新啟動它如果那麼做則會拋出 InvalidThreadStateException 異常
四個常見缺陷
正如我已經展示過的Java 語言中的多線程編程是通過語言支持的大量精心設計的構造實現的另外還設計了大量設計模式和指導原則來幫助人們了解這種復雜性帶來的許多缺陷除此之外多線程編程會很容易地在不經意間把細微的 bug 帶進多線程代碼而且更重要的是這類問題分析和調試起來非常困難接下來要介紹的是用 Java 語言進行多線程編程時將會遇到(或者可能已經遇到過)的最常見問題的一個列表
[] []
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27771.html