1. 程式人生 > >高階程式設計師需知的併發程式設計知識(一)

高階程式設計師需知的併發程式設計知識(一)

## 併發程式設計簡介 併發程式設計式Java語言的重要特性之一,當然也是最難以掌握的內容。編寫可靠的併發程式是一項不小的挑戰。但是,作為程式設計師的我們,要變得更有價值,就需要啃一些硬骨頭了。因此,理解併發程式設計的基礎理論和程式設計實踐,讓自己變得更值錢吧。 ### 使用併發程式設計的優勢 #### 1、充分利用多核CPU的處理能力 現在,多核CPU已經非常普遍了,普通的家用PC基本都雙核、四核的,何況企業用的伺服器了。如果程式中只有一個執行緒在執行,則最多也只能利用一個CPU資源啊,如果是一個四核的系統,豈不是最多隻利用了25%的CPU資源嗎?嚴重的浪費啊! 另外如果存在I/O操作的話,單執行緒的程式在I/O完成之前只能等著了,處理器完成處於空閒狀態,這樣能處理的請求數量就很低了。換成多執行緒就不一樣了,一個執行緒在I/O的時候,另一個執行緒可以繼續執行,處理請求啊,這樣,吞吐量就上來了。 #### 2、方便業務建模 如果在程式中只包含一種型別的任務,那麼比包含多種不同型別的任務的程式要更易於編寫、錯誤更少,也更容易測試。如果在業務建模中,有多種型別的任務場景。我們可以使用多執行緒來解決,讓每個執行緒專門負責一種型別的任務。 通過使用執行緒,可以將負責並且一步的工作流進一步分解為一組簡單並且同步的工作流,每個工作流在一個單獨的執行緒中執行,並在特定的同步位置進行互動。 ### 併發程式設計帶來的風險 雖然併發程式設計幫助我們提高了程式的效能,同時也提高對我們程式設計師的要求,因為在編寫併發程式的過程中,一不小心就面臨著多執行緒帶來的風險。這些風險主要是安全性問題、活躍性問題和效能問題。 #### 1、安全性問題 安全性問題可能是非常複雜的,在多執行緒場景中,如果沒有正確地使用同步機制,會導致程式結果的不確定性,這是非常危險的。 比如我們熟知的 **count++** 問題 ```java public class UnsafeCount { private static int count; public int getCount(){ return count++; } } ``` 上面的程式碼,在單執行緒環境中沒有問題。但是如果是多個執行緒同時訪問getCount方法,則不會得到期望的正確結果。 原因在於count ++ 不是CPU級別的原子指令,我們寫了一條語句,但是在底層實際上包含了三個獨立的操作:讀取count,將count加1,將計算結果再寫會主記憶體。而這多個執行緒有機會在其中任何一個操作時發生切換,這樣便有可能兩個執行緒拿到了同樣的值,讓後執行加1的操作。 #### 2、活躍性問題 當某個操作無法繼續執行下去的時候,就會發生活躍性問題。在序列程式中,活躍性問題形式之一可能是無意中造成的無限迴圈。在多執行緒場景中,如果有執行緒A在等待執行緒B釋放其持有的資源,而執行緒B永遠都不釋放該資源,那麼執行緒A將永遠地等待下去。 **多執行緒中的活躍性問題一般指的就是死鎖、飢餓、活鎖等。** #### 3、效能問題 本來是用多執行緒是為了提高程式效能的,結果卻產生了效能問題。效能問題包括多個方面,例如服務時間過長,響應不靈敏,吞吐量過地、資源消耗過高等。 使用多執行緒而產生效能問題的根本原因就是,建立執行緒、切換執行緒都是要帶來某種執行時開銷的。如果我們的程式在頻繁的建立執行緒,那很快建立執行緒的消耗將增加,拖累程式整體效能。同時頻繁的執行緒切換,也會產生效能問題。 ## 建立執行緒的幾種方法 在使用Java開始編寫併發程式時,我們首先要知道在Java中應該如何建立執行緒,至少有下面的三種方法。通過執行緒池建立執行緒留到後面執行緒池章節單獨說明。 ### 實現Runnable介面 我們通過實現一個Runnable介面,將執行緒要執行的任務封裝起來。 ```java public class MyTask implements Runnable{ public void run() { // 要實行的任務 } } ``` 使用Thread物件啟動執行緒 ```java public class MyTaskThread { public static void main(String[] args) { Thread thread = new Thread(new MyTask()); thread.start(); } } ``` ### 實現Callable介面 可以看到實現Runnable介面啟動的執行緒是沒有返回值的。而Callable介面可以實現有返回值地啟動執行緒。 ```java public class MyCallableTask implements