在咱們日常的開發工做中,或多或少的都能接觸到多線程編程或者一些併發問題,隨着操做系統和系統硬件的升級,併發編程被愈來愈多的運用到咱們的開發中,咱們使用多線程的最初的想法是可以更大程度的利用系統資源,可是咱們在使用多線程的時候,也會有一些問題的存在,咱們先來看一段代碼。java
private static int i = 0; private static void increse(){ i++; } public static void main(String[] args) { Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++){ increse(); } } }); threads[i].start(); } while (Thread.activeCount() > 1){ Thread.yield(); } System.out.println(i); }
首先看看這段代碼是沒有問題的,可是若是在多線程的環境中,這段代碼運行的結果基本是都不同的,這裏是開啓20個線程,而後每個線程調用increse()
方法對變量i
進行一個賦值操做,預期的一個輸出應該是200000
,可是爲何會每一次的輸出都不太同樣呢?緣由就在於這個地方i++
,這裏就是產生併發問題的根本緣由,那看起來很簡單的一個i++
爲何會有這種問題?這裏就簡單的從java內存模型
(JMM)來了解一下,首先JMM
規定,每個線程運行時都會有一個工做內存,而後變量i
是存儲在主內存的,每次線程在計算數據的時候都要去主內存中獲取當前變量的值,那麼簡單的來講,就是一個線程將變量i
計算獲得結果後,尚未將這個數據刷新到主內存,在這個時候,其餘的線程已經獲取到了原來的值,換句話說,本線程中獲取到的數據是不是最新的,這個是不知道的。可是這只是從JMM
角度來簡單的說一下,i++
這個看似簡單的操做其實包含了三個操做,獲取i
的值,對i
進行自增,而後對i
進行賦值。就是在這幾個操做中,其餘的線程會有不少的時間來作不少的事情,那有人會問,是否是將這個i++
操做同步就能夠了?是的,那該如何同步呢?編程
有人說用volatile
來修飾一下變量i
不就能夠了麼?是的,java
中volatile
這個關鍵字確實是提供了一個同步的功能,可是爲何在這裏修改一下仍是沒有效果呢?緣由就在於若是一個變量用volatile修飾以後,只是會讓其餘的線程當即可以知道當前變量的值是多少,這裏就叫作可見性
,可是仍是解決不了i++
這幾個操做的問題,那如何處理,又有人提出用synchronized
這個關鍵字,不得不認可,這個關鍵字確實很強大,是可以解決這個問題,那咱們有沒有考慮過這個爲何能夠解決這個問題,是如何解決的,那仍是簡單的說一下,首先synchronized
是屬於JVM級別的,有這個關鍵字的方法或者代碼塊,最後會被解釋成monitorenter
和monitorexit
指令,這兩個字節碼都明確須要一個reference
類型的參數來指出要鎖定或者解鎖的對象,像這樣:安全
public synchronized String f(){ //code } synchronized(object){ //code }
看到這裏,咱們應該能明白synchronized
爲何能夠解決上面程序的問題,可是咱們還應該要明確一個概念就是原子性
,換句話說,就是咱們在處理一些多線程的問題的時候,應該保證一些共享數據的操做是原子性的,這樣才能保證正確性,看到這裏,相信你也有了一個大概的理解,那咱們來總結一下,在處理多線程的問題的時候,哪些點是值得注意的,可見性
,原子性
,有序性
,這幾個點是保證多線程可以正確的一個前提條件,至於什麼是有序性,這裏涉及到內存指令的重排序,不在討論範圍內,之後再來討論。多線程
這裏還要指出一個問題,就是是否咱們在處理多線程問題的時候,必定要同步,或者說必定要加鎖,這個也不是必定的,以前網上有一個說笑的方式,就是咱們在處理多線程的問題的時候,有時候就會發現,代碼又被寫成了單線程,固然這只是一個玩笑話,可是這裏咱們也能看出來,是否是單線程的程序就不會有這些問題?答案是確定的,由於單線程不存在資源競爭的問題,也就不須要再討論了。併發
那麼咱們何時須要使用同步,何時又不須要呢?咱們來看一段代碼ide
public String f(String s1, String s2, String s3){ return s1 + s2 +s3; }
這是一個字符串拼接的一個方法,咱們來反編譯看一下,這裏JVM究竟是怎麼作的?優化
這裏很明顯的可以看出來,最後是經過StringBuilder來爲咱們生成了最後的結果,那有人會問,這裏線程安全麼?是的,這裏是線程安全的,由於在這個方法中,雖然也有變量的使用,可是都是屬於線程內部在使用,其餘的線程根本不會訪問到或者說這些變量也不會讓其餘線程訪問到,咱們稱其爲沒有方法逃逸
,也就是說只能在本線程中使用這些變量,這裏是線程安全的,至於什麼是逃逸分析
,簡單的提一下就是這是JVM的高級優化的一種方式,說的再簡單一點,就是別的線程訪問不到這個變量,這樣的代碼是不須要同步的。ui
在多線程的問題上面概念比較多,也須要慢慢理解,其實JVM也在多線程的鎖的上面作了不少優化,還有互斥同步
和非互斥同步
,還有不少概念,什麼是自旋和自適應自旋
,鎖消除
(順便提一下,上面的字符串拼接的例子就是用到了這種優化方式),鎖粗化
,咱們下次再繼續分享。spa