前段時間一直在學習多線程相關的知識,目前也算有了一個總體的認識,今天呢,主要從總體介紹一下,只談造火箭,擰螺絲這種細節還須要本身深究。html
首先是操做系統級別對於多線程的支持,由 CPU 的多級緩存、緩存一致性、亂序執行優化等問題而設計出 Java 內存模型。關於這部分我前面已經總結過。算法
說完了操做系統級別的多線程的後備知識以及 Java 內存模型的設計,接着說說 多線程的實現以及Java 中的多線程是怎麼實現的,具體能夠看這篇。多線程
多線程實現原理工具
上面說的都是一些原理,深層次的東西,你如果不懂,也不影響你使用 Java 多線程來編碼,無非就是繼承 Thread 實現 Runnable 。可是呢,使用多線程,不可避免的就會出現線程安全問題。性能
雖然說咱們也知道,無論三七二十一我使用 synchronized 關鍵字就好使,可是呢,想要知其因此然的同窗仍是須要好好學習一下上面說的那些原理的。並且,處理線程安全問題會涉及到性能問題,這就是高手之間的差異了。學習
這麼一說,咱們的重點就放在如何保證線程安全的問題上了,首先,線程安全的特性有 3 個,原子性,可見性,有序性。咱們主要看看 Java 中都是使用什麼方式來保證這 3 個特性的。優化
主要的體如今 atomic 包、CAS 思想、synchronized 鎖、Lock 鎖、volatile 關鍵字、還有一個很是重要的 JUC 包,別急,後面一個一個的說。ui
哎,發現我已經說了 atomic 和 CAS 了,atomic包、synchronized | Java 中線程安全 關於 volatile 關鍵字,在 JVM 中的語義,即爲防止指令重排並添加內存屏障,簡單來講就是限制指令的執行順序,並默認添加一些指令(內存屏障)以達到有序和可見性的目的。比方說在 volatile 變量讀操做以前必須先完成寫。
簡單提一下,使用 volatile 修飾的變量不必定是線程安全的,除非對變量的操做都是原子性的。
在說 JUC 以前,先說一種比較討巧的手段來處理多線程問題,咱們知道多線程中存在問題主要就是在不一樣線程之間對共享變量進行操做,使得數據變髒。那咱們在多線程中杜絕出現共享變量就好了呀,基於此,也就出現了 ThreadLocal 這個類。
ThreadLocal 也被稱爲線程局部變量,實際上它是封裝了一個當前線程爲 K 的一個 Map,將須要用到的變量存進 Map,用的時候取出來,而這個 Map 又是線程私有的,其它線程沒法訪問,天然是線程安全的。
使用 ThreadLocal 有個實際的例子,咱們在過濾器中添加變量到 ThreadLocal 中,在攔截器中刪除 ThreadLocal 中的值,這個變量多是用戶惟一性的標識或是其它,由於每個請求都會觸發一個線程執行,通過過濾器以後設置好變量,在方法中使用,而在攔截器中對請求處理以後便可對 ThreadLocal 中的數據進行清除,否則會發生內存泄漏。
爲何 ThreadLocal 能夠保證線程安全,由於它存放的變量都是線程私有的,還有一種咱們常常定義的變量也是線程私有的,那就是咱們定義的局部變量,方法內的變量。由於這些變量是存放在線程棧上面,這部分區域是線程私有的,也就是咱們以前說的工做內存的概念,因此咱們常常寫局部變量,而歷來沒有考慮過線程安全問題,實際緣由在這。
線程私有能夠保證線程安全,可是全局變量又是少不了一環,偶爾定義了那麼一個,一不留神,在多線程環境中可能就會出錯。最最常說的無非就是那幾個集合類和 String 相關的類,固然還有其它的不安全的類,可能你已經使用了但還不知道。
既然知道不安全了,那在多線程的環境中就避免使用它,而貼心的 JDK 也提供了一套與之對應的安全的類給咱們使用,咱們要作的就是知道哪些類是不安全的,對應的安全的類是什麼以及如何保證的安全。
先說 String、StringBuffer、StringBuilder 之間細微的差異,String 是不可變類,不可變類自己就是定義的時候就是線程安全的,關於不可變類這又是一塊知識,暫時不說了。
可是也正是由於 String 的不可變,因此每次對字符串的修改都會新建立一個字符串,而最終咱們想要獲得的可能就是最後的一個值,中間那些中間值太佔空間。嗯,優化以後就有了後面兩兄弟。
簡單說 StringBuffer 是線程安全的,而 StringBuilder 是不安全的,這兩個較 String 都是可變對象。因此說,在單線程中使用 StringBuilder 代替 String ,多線程中使用 StringBuffer 代替 String 是個不錯的選擇。對了,StringBuffer 保證線程安全的手段也就是使用 synchronized 方法。
還有就是關於集合類相關的線程安全操做,有一個 Collections 工具類,提供了一套 synchronizedXXX 方法,好吧,都寫在臉上了,這樣一來,List、Set、Map 都搖身一變安全了。
還有一些古老的實現類,像什麼 Vector,Hashtable,也是能夠實現線程安全的,可是一來是基本不使用,二是有些狀況下可能並不能保證線程安全,像 Vector 中的 add 和 remove 方法就是沒有進行同步操做,在某些狀況下就會出現不安全。
不知道大家有沒有發現,這些古老的實現類中實現安全的方式無非就是添加 synchronized 關鍵字。有沒有高級一點的操做啊,確定有,可是今天扯不動了,下次再說吧!
簡單總結一下,今天說了什麼,回顧以前的文章並理順了總體的思路,從底層原理到 JMM,從多線程的實現到如何保證線程安全,又舉例說明了 JDK 中類的設計思路,後面的 JUC 設計的更秀。
說的挺多,可是萬變不離其宗,作好分類,學習記憶更便捷,Java 保證線程安全底層看 3 大特性,語言實現層面主要看鎖,鎖也就兩大類 synchronized 和 Lock,只不過度支極多而已,還有一層是算法層面,像什麼 CAS 。可是,回到 CPU 時代還他麼都是 0、1 代碼,因此,不要慫就是幹!