1、併發和並行:
併發是同一時間應對(dealing with)多件事情的能力;
並行是同一時間作(doing)多件事情的能力。程序員
2、並行架構:
位級並行,32位計算機的運行速度比8位計算機更快,由於並行,對於32位數的加法,8位計算機必須進行屢次8位計算,而32位計算機能夠一步完成,即並行的處理32位的4字節。
指令級(instruction-level)並行,程序員一般能夠不關心處理器內部並行的細節,由於儘管處理器內部的並行度很高,可是通過精心設計,從外部看上去全部處理都像是串行的。
數據級(data)並行,數據級並行(也稱爲「單指令多數據」,SIMD)架構,能夠並行地在大量數據上施加同一操做。這並不適合解決全部問題,但在適合的場景卻能夠大展身手。
任務級(task-level)並行,終於來到了你們所認爲的並行形式——多處理器。從程序員的角度來看,多處理器架構最明 顯的分類特徵是其內存模型(共享內存模型或分佈式內存模型)。編程
對於共享內存的多處理器系統,每一個處理器都能訪問整個內存,處理器之間的通訊主要經過內存進行。
對於分佈式內存的多處理器系統,每一個處理器都有本身的內存,處理器之間的通訊主要經過網絡進行。
用併發的目的,不只僅是爲了讓程序並行運行從而發揮多核的優點。若正確使用併發,程序還將得到如下優勢:及時響應、高效、容錯、簡單。安全
注意:不該該在產品代碼上,使用Thread類等底層服務。網絡
3、七個模型多線程
一、線程與鎖:線程與鎖模型有不少衆所周知的不足,但還是其餘模型的技術基礎,也是不少並 發軟件開發的首選。 二、函數式編程:函數式編程日漸重要的緣由之一,是其對併發編程和並行編程提供了良好的支 持。函數式編程消除了可變狀態,因此從根本上是線程安全的,並且易於並行執行。 三、Clojure之道——分離標識與狀態:編程語言Clojure是一種指令式編程和函數式編程的混搭方 案,在兩種編程方式上取得了微妙的平衡來發揮二者的優點。 四、actor:actor模型是一種適用性很廣的併發編程模型,適用於共享內存模型和分佈式內存模型, 也適合解決地理分佈型問題,能提供強大的容錯性。 五、通訊順序進程(Communicating Sequential Processes,CSP):表面上看,CSP模型與actor模 型很類似,二者都基於消息傳遞。不過CSP模型側重於傳遞信息的通道,而actor模型側重於通道 兩端的實體,使用CSP模型的代碼會帶有明顯不一樣的風格。 六、數據級並行:每一個筆記本電腦裏都藏着一臺超級計算機——GPU。GPU利用了數據級並行, 不只能夠快速進行圖像處理,也能夠用於更廣闊的領域。若是要進行有限元分析、流體力學計算 或其餘的大量數字計算,GPU的性能將是不二選擇。 七、Lambda架構:大數據時代的到來離不開並行——如今咱們只須要增長計算資源,就能具備 處理TB級數據的能力。Lambda架構綜合了MapReduce和流式處理的特色,是一種能夠處理多種大數據問題的架構。
4、線程與鎖:架構
class Counter { private int count = 0; public synchronized void increment() { ++count; } public int getCount() { return count; } } 毋庸置疑,對於增長了同步功能的代碼,每次執行都將獲得正確結果,但代碼中仍隱藏了一個bug。 潛藏的bug是: 除了increment()以外,getCount()方法 也須要進行同步。 不然,當一個線程對值的修改沒有及時更新到主內存,從而致使 調用getCount()的線程可能得到一個失效的值。 解釋: Java內存模型定義了什麼時候一個線程對內存的修改對另外一個線程可見。 基本原則是,若是讀 線程和寫線程不進行同步,就不能保證可見性。 然而兩個線程都須要進行同步。只在其中一個線程進行同步是不夠的, 競態條件: 計算的正確性取決於多個線程的交替執行時序時,就會發生競態條件。 一、亂序執行。執行依賴於檢測的結果,而檢測結果依賴於多個線程的執行時序。 亂序緣由: 編譯器的靜態優化能夠打亂代碼的執行順序; JVM的動態優化也會打亂代碼的執行順序; 硬件能夠經過亂序執行來優化其性能。
因此在多線程環境下,對一個文件的操做須要加鎖。併發
二、延遲初始化: 線程A和線程B同時執行getInstance,可能會取到兩個實例對象,主要看線程執行時序了。 public class ObjFactory { private Obj instance; public Obj getInstance(){ if(instance == null){ instance = new Obj(); } return instance; } }
5、來自外星方法的危害編程語言
規模較大的程序經常使用監聽器模式(listener)來解耦模塊。 在這裏,咱們構造一個類從一個URL 進行下載,並用ProgressListeners監聽下載的進度。 public class Downloader extends Thread { private InputStream in; private OutputStream out; private ArrayList<ProgressListener> listeners; public Downloader(URL url,String outputFilename) throws IOException { in=url.openConnection().getInputStream(); out = new FileOutputStream(outputFilename); listeners=new ArrayList<ProgressListener>(); } public synchronized void addListener(ProgressListener listener){ listeners.add(listener); } public synchronized boolean remove(ProgressListener listener){ return listeners.remove(listener); } /*** * 來自外星方法的危害 * * addListener()、removeListener()和updateProgress()都是同步方法, * 多線程能夠安全地使用這些方法。儘管這段代碼僅使用了一把鎖,但仍隱藏着一個死鎖陷阱。 * * 陷阱在於updateProgress()調用了一個外星方法——但對於這個外星方法一無所知。外星方法能夠作任何事情, * 例如持有另一把鎖。這樣一來,咱們就在對加鎖順序一無所知的狀況下使用了兩把鎖。就像前面提到的,這就有可能發生死鎖。 * * @param n */ private synchronized void updateProgress(int n){ for(ProgressListener listener:listeners){ listener.onProgress(n); } } /*** * 一種方法是在遍歷以前對listeners進行保 護性複製(defensive copy), * 再針對這份副本進行遍歷 * 這是個一石多鳥的方法。不只在調用外星方法時不用加鎖,並且大大減小了代碼持有鎖的時間。 * 長時間地持有鎖將影響性能(下降了程序的併發度),也會增長死鎖的可能。 * @param n */ private void updateProgress2(int n){ ArrayList<ProgressListener> listenersCopy=null; synchronized (this){ listenersCopy=(ArrayList<ProgressListener> )listeners.clone(); } for(ProgressListener listener:listenersCopy){ listener.onProgress(n); } } @Override public void run(){ int n = 0, total = 0; byte[] buffer = new byte[1024]; try { while((n = in.read(buffer)) != -1) { out.write(buffer, 0, n); total += n; updateProgress(total); } out.flush(); } catch (IOException e) { e.printStackTrace(); } } }