併發指在宏觀上的同一時間內同時執行多個任務。爲了知足這一需求,現代的操做系統都抽象出 線程 的概念,供上層應用使用。java
這篇博文不打算詳細展開分析,而是對java併發中的概念和工具作一個梳理。
沿着併發模型、併發要解決的問題、基本工具、衍生工具這一思路展開。程序員
<!-- more -->算法
首先線程是什麼?線程是由OS抽象並實現的,咱們知道OS的職責是管理併合理分配硬件資源,那麼OS爲了更好的管理、分配CPU資源,同時也爲了知足同時執行任務這一需求,設計了線程這一律念。編程
雖然java程序運行在JVM虛擬機上,可是java的線程仍然是對操做系統原生線程的封裝,同時,jvm對線程實現時也將jvm的運行棧設計成線程私有內存,所以,java線程和原生線程在理解上實際上沒太大區別。緩存
線程的五種狀態:安全
graph LR 新建 --> 就緒; 就緒 --> 運行; 運行 --> 就緒; 運行 --> 阻塞; 阻塞 --> 就緒; 運行 --> 死亡;
先來看上面的就緒狀態和運行狀態。咱們知道線程雖然宏觀上是同時執行的,可是微觀上使用如時間片輪轉算法使得線程依次執行。那麼,同一時間只有一個線程執行,其它須要執行的線程處於 就緒隊列 中,等待本身被調度到。數據結構
而若是線程想要暫時放棄在CPU上運行的權利,就會阻塞本身。這時對應着阻塞狀態,同時線程會從就緒隊列中移除,進入等待隊列。
很顯然,阻塞線程被喚醒確定是進入就緒隊列等待調度,而不多是直接分配到CPU上運行。多線程
在線程同步時,線程可能因爲如下狀況被阻塞:併發
線程同步面臨兩個問題,想象下有兩個線程在協做工做完成某項任務。那麼須要解決如下問題:jvm
在多線程程序的性能問題上,若是是對於一樣一段臨界區的多線程訪問,那麼則有如下幾個思路:
以上三種思路的性能優劣沒有一個普適的結果,和具體的場景相關。
併發中還會出現如下幾種狀況致使系統不可用:
併發編程中須要考慮的幾個概念:
從我我的的理解來看,原子性屬於由併發和線程這一理論概念天然而然推導衍生而來的概念,而可見性和有序性是具體的工程實踐中產生的。
實際中,jvm並不能實現的特別完美,總會有工程上的妥協。理論模型與實際模型沒法完美契合,總存在必定的誤差。
好比說,jvm爲了向性能妥協使用了緩存機制,犧牲了數據一致性,這就產生了可見性的概念,須要程序員編程時本身控制。
jvm爲了指令更高效率的執行進行了指令重排優化,則產生了有序性的問題。印象裏之前大學裏學過的CPU的流水線技術,爲了指令可以更好的被CPU流水線利用,減小流水線的空閒時間,編譯器編譯時也會在不影響 串行語義 的前提下,進行指令重排。
總而言之,這是在性能和理論模型完整性之間的一種妥協。
技術上的工具、概念繁多複雜,可是若是咱們能理解技術設計上無時無刻的不運用抽象和分層的手段,
那麼,咱們能夠把技術上的工具分爲兩種:
更高層次的工具對基礎工具進行了抽象和封裝,屏蔽了其中的實現細節。
這裏想強調的是,工具的接口和實現是分開的,二者能夠沒有關係。
如java的監視器鎖從接口上來看,其語義和互斥鎖同樣。然而它並不必定使用互斥鎖實現,而是能夠爲了性能存在優化,只要最終的行爲與接口相同便可。
有三種用於線程同步的工具:
鎖。鎖可用於規定一個 臨界區,同一時間臨界區內僅能由一個線程訪問。其餘線程則在臨界區外等待(阻塞)。
在java中,Object類有wait()、notify()和notifyAll()之類的方法。
這些方法能夠認爲每一個對象都內置了一個條件變量,而這些方法是對這些條件變量的操做,所以,可使用這些方法將對象看成條件變量使用,從而作到線程的同步。
底層機制的特色直接獲得的:
1. java中的volatile關鍵字。 2. CAS。
volatile關鍵字可以保證變量的可見性,或者說是讀或寫的原子性。
CAS即compareAndSwap,原子操做 。
CAS操做直接可以對應到單條CPU指令,所以自然具備原子性。java中是經過JNI調用C語言從而調用CPU底層指令實現。
CAS的行爲和如下代碼一致:
int cas(long *addr, long old, long new) { if (*addr == old) { *addr = new; return 1; } else { return 0; //* } }
那麼CAS能夠作什麼呢?不少樂觀併發控制能夠基於CAS實現。
好比說,經過一個標記變量來記錄臨界區被誰佔有,線程進入臨界區前不斷的使用CAS操做判斷標記變量是否爲空同時將其記錄爲本身,來實現鎖機制。這就是自旋鎖的思路。
除此以外,樂觀鎖也能用CAS實現,好比java的Atomic系列,就是這樣實現的。
前面說到能夠認爲每一個對象內置一個條件變量,一樣,每一個對象也內置一個鎖。這個內置鎖在Java中被抽象爲監視器鎖(monitor)。
synchronized關鍵字的使用實際上就至關於使用監視器鎖定義了一個臨界區。使用這種語法也特別直觀簡單,因此java常常用synchronizd來進行線程的同步。
JDK1.6以後,爲了提高監視器鎖的性能,java經過某些手段進行了優化。其中包含鎖優化機制,對應三種鎖:
1. 偏向鎖 2. 輕量級鎖 3. 重量級鎖
一開始只有一個線程使用線程時使用偏向鎖,當存在多個線程使用時膨脹爲輕量級鎖,而出現比較多的線程競爭時再膨脹爲重量級鎖。
Vector
、 ConcurrentHashMap
等。ReentrantReadWriteLock
。其它:
雙重檢查鎖
爲了表達某種鎖的特色,也會有着不少的概念。
可是這種概念對應的不是某一種鎖,而是一類擁有特定屬性的鎖。如: