併發編程,即多條線程在同一時間段內「同時」運行。編程
在多處理器系統已經普及的今天,多線程能發揮出其優點,如:一個8核cpu的服務器,若是隻使用單線程的話,將有7個處理器被閒置,只能發揮出服務器八分之一的能力(忽略其它資源佔用狀況)。
同時,使用多線程,能夠簡化咱們對複雜任務的處理邏輯,下降業務模型的複雜程度。安全
所以併發編程對於提升服務器的資源利用率、提升系統吞吐量、下降編碼難度等方面起着相當重要的做用。服務器
以上是併發編程的優勢,可是它一樣引入了一個很重要的問題:線程安全。多線程
線程在併發執行時,由於cpu的調度等緣由,線程會交替執行。以下圖例子所示併發
public class SelfIncremental { private static int count; public static void main(String[] args) { Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { count++; System.out.println(count); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { count++; System.out.println(count); } }); thread1.start(); thread2.start(); } }
執行完畢後count的值並非每次都能等於20000,會出現小於20000的狀況,緣由是thread1和thread2可能會交替執行。學習
如圖所示:編碼
由於count++ 不是一個原子操做,實際上會執行三步:spa
所以在併發執行時,兩個線程同時讀,可能會讀取到相同的值,對相同的值加一,致使結果不符合預期,這種狀況就是線程不安全。線程
線程安全:當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些線程將如何交替執行,而且調用時不須要採用額外的同步操做,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。code
引起線程安全性問題的緣由主要是共享內存能夠被多個線程讀寫,由於讀取和修改時機存在不肯定性,致使有線程讀到了過時數據,並在髒數據的基礎上處理後寫回共享內存,產生了錯誤的結果。
竟態條件
在併發編程中,由於不恰當的執行時序而出現不正確的結果的狀況被稱爲竟態條件。
常見的靜態條件類型:
發佈與逸出
發佈:使對象可以在當前做用域以外的代碼中使用。如將該對象的引用保存到其它代碼能夠訪問的地方、在一個非私有的方法中返回該引用,將引用傳遞到其它類的方法中。如:
public static Student student; public void init() { student = new Student; }
這裏 student對象就被髮布了。
逸出:當不應被髮布的對象被髮布了,就稱爲逸出。如
private String name = "xxx"; public String getString() { return name; }
這裏name原爲private類型可是卻被getString方法發佈了,就能夠被視爲逸出。
線程封閉
線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,而且只有這個對象能修改。
線程封閉即不共享數據,僅在單線程內訪問數據,這是實現線程安全最簡單的方式之一。
實現線程封閉能夠經過:
只讀共享
在沒有額外同步的狀況下,共享的對象能夠由多個線程併發訪問,可是任何線程都不能修改。共享的對象包括不可變對象和事實不可變對象。
不可變對象:若是某個對象在被建立後就不能修改,那麼這個對象就是不可變對象。不可變對象必定是線程安全的。
線程安全共享
線程安全的對象在其內部實現同步,所以多線程能夠經過對象的公有接口來進行訪問而不須要本身作同步。
保護對象
被保護的對象只能經過持有特定的鎖來訪問。即經過加鎖機制,確保對象的可見性及原子性。
本文主要是記錄了學習《Java併發編程實站》前幾章中,併發編程相關的一些概念。簡單介紹了線程安全、鎖機制等,接下來 咱們會深刻JUC源碼,來深入學習併發編程相關知識。
備註:本文主要源自對《Java併發編程實戰》的學習筆記。
本文爲雲棲社區原創內容,未經容許不得轉載。