前言
今天回家火車上沒事情幹,一直在看石杉碼農老大的技術博客。其中看了些併發系列的文章,雖然都是之前學習JDK源碼的時候都瞭解到的東西,可是隔久了發現本身不是很清晰了,因此到家後把本身筆記撈出來複習一遍,把知識點再串聯一次,讓本身理解更深入一點。緩存
併發問題
併發問題是指多線程讀寫共享內存的時候,產生結果不可預期的狀況,併發問題的產生的緣由能夠歸結爲兩種,一是由於競態條件,二是由於內存可見性、多線程
競態條件
什麼是競態條件
競態條件,官方解釋是若是程序運行順序的改變會影響最終結果,這就是一個競態條件。併發
這句話有點抽象,描述的有點抽象,我我的對競態條件的理解是多個線程在競爭同一系列資源的使用權,由於使用都是有時間的,不是咔嚓一下完成的,對於每一個線程來講它可以正確執行的條件就是要求從獲取資源到使用資源完成都是原子性的,不容許其餘線程來中途改變資源從而影響了原子性,每一個線程都須要這種條件,我把這種多個線程對於共享資源獲取到操做完成的原子性的需求叫作競態條件。函數
針對競態條件問題的應對思想
結合本身所學,總結一下應對競態條件的思想:學習
- 經過路由避免競爭,這反映在併發包中有ConcurrentHashMap的分段鎖機制、Java8對Cas的優化如LongAdder代替AtomicLong等等,不少地方都是運用了這種思想下降競爭資源粒度。
- 對資源的獲取以及使用資源進行串行化,經過鎖、CAS、隊列來串行化獲取資源和操做資源的操做。
- 寫時複製避免讀寫競爭。好比ArrayList的寫操做,沒有必要在寫的過程不讓讀,經過寫時複製是能夠同時進行讀的。對應的併發容器CopyOnWriteArrayList就是採用的寫時複製原理使得隨時均可以讀,相似的還有InnoDb的MVCC快照讀、各類讀緩存機制如eureka的多級緩存機制。等下講到的JVM針對共享變量主內存、工做內存都是相似的思想。
內存可見性
什麼是內存可見性
先看這張圖:優化
![](http://static.javashuo.com/static/loading.gif)
如圖,在Java內存模型中,對於共享變量data,兩個工做線程都須要讀取這個值的時候,其實是讀取和操做的副本,相似高速本地緩存,這樣作的緣由是使用緩存機制下降對data的讀寫併發衝突,否則都去讀寫同一個內存地址,效率是很低的。線程
可是這樣形成的問題是,各個工做線程的變量不是即時同步的,如線程1將data改成了1,可是對於線程2來講可能他本身的data副本中尚未同步爲1,他讀取的仍是0。這就是內存可見性問題。對象
怎麼避免內存可見性致使的問題
- 一是使用synchronized同步鎖,鎖住共享數據。可是這種對於解決內存可見性來講較重。
- 二是使用volatile關鍵字,使用volatile關鍵字修飾時,可理解爲對數據的操做都在主存中進行,相比synchronized同步鎖,volatile關鍵字更輕量級一點。另外volatile除了解決了內存可見性,也禁止指令重排(好比懶漢式單例,在初始化一個對象賦值給一個變量的過程,可能分爲三步,第一步開闢內存空間,第二步調用構造函數初始化對象,第三步將變量指向分配的內存空間,當不加volatile關鍵字時沒法保證這三步的順序,因此多線程有可能對象不爲Null可是變量不爲null,可是卻沒有初始化,致使報錯)。