這一篇的內容主要來自於《java併發編程實戰》,有一說一,看這種寫的很專業的書不是很輕鬆,也沒辦法直接提升多少開發的能力,可是卻能更加夯實基礎,就像玩war3,熟練的基本功並不能讓你快速地與對方拉開差距,可是卻能再每一次團戰中積累優點。java
近年來,併發編程的領域更多的偏向於使用非阻塞算法,這種算法底層用原子機器指令(如比較交換CAS之類的)來替代鎖用以確保數據在併發訪問中的一致性。這樣的非阻塞算法普遍的用於在操做系統和JVM中實現線程/程序調用機制、垃圾回收算法等。算法
java5.0後,使用原子變量類(例如AtomicInteger和AtomicReference)來構建高效的非阻塞算法。編程
與基本類型的包裝類相比原子變量類是可修改的,在原子變量類中沒有從新定義hashCode或equals方法,每一個實例都是不一樣的,不宜用作基於散列的容器中的鍵值。安全
原子變量類比鎖的粒度更細量級更輕,將發生競爭的範圍縮小到單個變量上。併發
從《併發編程實戰》這本書出發,對給予的用例進行測試,可以得出的結論:原子變量因其使用CAS的方法,在性能上有很大優點。性能
以前的文章已經講過了volatitle變量、CAS算法、AtomicInteger線程安全的原子變量等,沒有看過或者已經忘記的的同窗能夠點擊下方的藍色連接去看看(複習一下)。測試
若是一個線程的失敗或者掛起不會致使其餘線程的失敗或掛起,這種算法就叫作非阻塞算法。操作系統
在併發訪問的環境下,push和pop方法經過CAS算法能夠保證棧的原子性和可見性,從而安全高效的更新非阻塞棧。.net
//根據《併發編程實戰》的代碼進行分析 public class CocurrentStack<E> { /** * AtomicReference和AtomicInteger很是相似,不一樣之處就在於AtomicInteger是對整數的封裝, * 而AtomicReference則對應普通的對象引用。也就是它能夠保證你在修改對象引用時的線程安全性 */ AtomicReference<Node<E>> top = new AtomicReference<Node<E>>(); /* * 這裏定義了一個棧(實際上是列表,可是咱們提供的功能僅僅能做爲棧), * 當有新值加入,會把舊值掛在新值的next方法上,,能夠經過遞歸next拿到全部Node * */ public void push(E item) { Node<E> newHead = new Node<E>(item); Node<E> oldHead; do { oldHead = top.get(); newHead.next = oldHead; //這邊用了CAS算法進行判斷,這也是非阻塞算的和核心之一 } while (!top.compareAndSet(oldHead, newHead)); } /** * 實現出棧功能,同時出棧也實現了CAS的功能 */ public E pop() { Node<E> oldHead; Node<E> newHead; do { oldHead = top.get(); if(oldHead == null) { return null; } newHead = oldHead.next; } while (!top.compareAndSet(oldHead, newHead)); return oldHead.item; } private static class Node<E> { public final E item; public Node<E> next; private Node(E item) { this.item = item; } } }
根據代碼咱們能夠看出它擁有非阻塞算法的特色:一個線程的失敗或者掛起不會致使其餘線程的失敗或掛起。若是某項操做的完成具備不肯定性,不成功則會從新執行。
這個非阻塞棧經過CAS來嘗試修改棧頂元素,該方法爲原子操做,若是發現被其餘線程干擾,CAS會更新失敗,這個時候意味着另外一個線程已經修改了堆棧。這些操做都是原子化地進行的。同時,咱們須要不斷的循環,以保證在線程衝突的時候可以重試更新。
根據CAS算法的內容與對非阻塞棧的研究,咱們知道要實現非阻塞算法的方法就是實現原子級的變量。
使用非阻塞算法實現一個連接隊列比棧更復雜,由於它須要支持首尾的快速訪問,須要維護兩個獨立的隊首指針和隊尾指針,初始時都指向隊列的末尾節點,在成功加入新元素時兩個指針都須要原子化的更新。
//依然是根據《併發編程實戰》的代碼進行分析 public class LinkedQueue <E> { private static class Node <E> { final E item; //仍是和以前的同樣,以保證你在修改對象引用時的線程安全性 final AtomicReference<Node<E>> next; public Node(E item, Node<E> next) { this.item = item; this.next = new AtomicReference<Node<E>>(next); } } //初始化節點 private final Node<E> dummy = new Node<E>(null, null); //聲明AtomicReference類型的頭尾節點、一切都是爲了安全 private final AtomicReference<Node<E>> head = new AtomicReference<Node<E>>(dummy); private final AtomicReference<Node<E>> tail = new AtomicReference<Node<E>>(dummy); /** *非阻塞算法新增操做 */ public boolean put(E item) { //聲明一個新的節點 Node<E> newNode = new Node<E>(item, null); while (true) { Node<E> curTail = tail.get(); Node<E> tailNext = curTail.next.get(); //獲得鏈表的尾部節點 if (curTail == tail.get()) { // 若是尾部節點的後續節點不爲空,則隊列處於不一致的狀態 if (tailNext != null) { // 比較後將爲尾部節點向後退進; tail.compareAndSet(curTail, tailNext); } else { // 若是尾部節點的後續節點爲空,則隊列處於一致的狀態,沒有其餘隊列操做,嘗試更新 if (curTail.next.compareAndSet(null, newNode)) { // 更新成功,將爲尾部節點向後退進; tail.compareAndSet(curTail, newNode); return true; } } } } } }
經過代碼,咱們能看出,隊列裏的每個節點都有一個空置節點。任何線程在執行插入操做的時候,都可以經過tail.next操做檢查隊列的狀態。若是不爲空的狀況,則能判斷出有其餘的線程已經插入了一個節點,可是尚未將tail指向最新的節點,這時候代碼能夠經過推動tail指針向前移動一個節點把狀態恢復爲穩定(即尾結點的置空狀態)。同時,另外一個已經執行一半的線程的尾結點恢復穩定後,也不會受到影響。
這種設計的好處在於,若是多個線程同時操做方法,不須要加鎖等待,每次插入以前鏈表自身會檢查tail.next是否爲空來斷定隊列是否須要保持穩定狀態,若是是,它首先會推動隊尾指針(可能屢次),直到隊列處於穩定狀態(tail.next爲空)。
咱們從源碼中也能看到,非阻塞鏈表ConcurrentLinkedQueue的實現方式。
非阻塞算法經過使用底層的併發原語,好比CAS算法,取代了鎖.原子變量類向用戶提供了這些低層級原語,也可以當作"更佳的volatile變量"使用,同時提供了整數類和對象引用的原子化更新操做.
非阻塞算法在設計和實現中很困難,可是在典型條件下可以提供更好的可伸縮性,並能更好地預防活躍度失敗。從JVM併發性能的提高很大程度上來源於非阻塞算法的使用,包括在JVM內部以及平臺類庫。
有須要的同窗能夠加個人公衆號,之後的最新的文章第一時間都在裏面,也能夠找我要思惟導圖