我們可能并沒有多少機會寫并發的東西,或者在非常精通之前最好還是使用優先使用最熟悉的,起嗎應該保證正確性才能討論性能問題,所以很多概念是理解性的。但理解這些概念會幫助我們理解優秀源碼(要不然別人的代碼都看不懂(?ε?))以及者寫程序時會有更多的思考。
已完成:
java內存模型
簡單的講,Java 內存模型將內存分為共享內存和本地內存。共享內存又稱為堆內存,指的就是線程之間共享的內存,包含所有的實例域、靜態域和數組元素。每個線程都有一個私有的,只對自己可見的內存,稱之為本地內存。java內存模型中的內存結構如下圖所示
內存模型.png
共享內存中共享變量雖然由所有的線程共享,但是為了提高效率,線程并不直接使用這些變量,每個線程都會在自己的本地內存中存儲一個共享內存的副本,使用這個副本參與運算。由于這個副本的參與,導致了線程之間對共享內存的讀寫存在可見性問題。
重排序
在執行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序,指令重排序
包括下面三種:
- 編譯器優化重排序,在不改變單線程程序語義的前提下。
- 指令級并行的重排序,如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。- 內存系統重排序,由于處理器可以使用緩存和讀寫緩沖區,這使得加載和存儲操作看起來可能是亂序執行的。這些重排序可能會導致多線程出現的內存可見性問題。
- 對于編譯器,JMM的編譯器重排序會禁止特定類型的重排序- 對于處理器重排序,JMM的處理器重排序規則會要求java編譯器在生成執行序列時,插入特定類型的內存屏障(Menory Barriers)指令,通過內存屏障指令來禁止特定類型的處理器重排序。>JMM屬于語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺上,通過禁止特定類型的編譯器重排序和處理器重排序為程序員提供一致的內存可見性保證。
上下文切換
首先我們知道,即使是單核的cpu也支持多線程的程序,cpu通過不停的給每個線程分配時間片來實現這個機制,這個時間片就是cpu分配給各個線程的執行時間,由于這個時間片非常的短,所以我們感覺好像就是多個線程在同時執行一樣,一般事件時間長為幾十毫秒。當執行完一個時間片后需要切換到下一個任務,在切換之前cpu需要保持現在這個線程的狀態,然后再去執行下一個線程,當cpu再次切換到原來的線程時,需要先讀取之前的任務的一個狀態,然后再繼續執行,這樣從保存到再加載就是一個上下文切換的過程。上下文的切換時需要開銷的,所以并不見得多線程就比單個線程快,而是應該根據具體的任務與硬件的配置來控制多線程的數量。線程過多可能造成CPU利用率達到100%。如果能夠減少上下文切換必然能提高程序的運行效率:
- 無鎖并發編程(比如:取模分段)
- CAS算法,Java的Atomic包采用此算法
- 合理使用線程>CAS(比較與交換,Compare and swap,是一種有名的無鎖算法函數) :對競爭資源不用加鎖,而是假設沒有沖突去完成某項操作,如果因為沖突失敗就不斷重試,直到成功為止。以此來減少上下文切換。上面所說的循環CAS操作就是上述所說的樂觀鎖。