《Java并發編程實戰》讀書筆記五:深入理解同步實現

一、構建自定義的同步工具

1. 內置的條件隊列

條件隊列就如同烤面包機上的面包已好的鈴聲。如果你正在聽著它, 當面包烤好后你可以立即注意到, 并且放下手頭的事情開始品嘗面包, 如果你沒有聽見它, 你會錯過通知消息, 但是回到廚房后還是看到面包的狀態, 如果已經烤完, 就取面包, 如果未烤完, 就再次監聽鈴聲。

條件隊列中的元素是一個個正在等待相關條件的線程。每一個對象都可以作為一個條件隊列,并且Object的wait,notify和notifyAll構成了內部條件隊列的API。

wait:wait是等待的意思,調用wait會自動釋放鎖,并請求系統掛起當前線程,從而使其他線程能夠獲得這個鎖
notify:發出通知,解除阻塞條件,JVM會從這個條件隊列上等待的多個線程選擇一個來喚醒
notifyAll:發出通知,解除阻塞條件,JVM會喚醒所有在這個條件隊列上等待的線程
條件謂語:線程等待的條件

條件等待中存在一個很重要的三元關系:synchronized,wait和一個條件謂語。
條件變量由一個鎖保護,檢查條件謂語時必須先持有鎖,調用wait和notifyAll所在方法的對象必須是同一個對象。

用下面這個代碼來解析:

public synchronized V take() throws InterruptedException{
    while(isEmpty()){
        wait();
    }
    V v = get();
    notifyAll();
    return v;
}

在這塊代碼中,isEmpty就是take()方法的條件謂語,A線程如果判斷為空,將會調用wait,阻塞A線程,直到其它線程B操作使isEmpty為false,接著B調用notifyAll,釋放鎖后,喚醒線程A。

notify和notifyAll的區別:
大多數情況下,應該優先選擇notifyAll。假如線程A在條件隊列上等待條件謂語PA,線程B在同一個條件隊列上等待條件謂語PB,假如線程C將PB變為真,且調用notify,JVM將從眾多的等待線程選擇其中A來喚醒,但是A看到PA仍然為false,于是繼續等待,然而線程B本可以開始執行,卻沒有被喚醒。

只有滿足一下兩個條件時,才能用單一的notify而不是notifyAll:
1. 所有等待線程的類型都相同
2. 單進單出:在條件變量上的每次通知,最多只能喚醒一個線程來執行
如果有10個線程在條件隊列中等待, 調用notifyAll會喚醒每一個線程, 讓它們去競爭鎖, 然后它們中的大多數或者全部又回到休眠狀態, 這意味著每一個激活單一線程執行的事件, 都會帶來大量的上下文切換, 和大量競爭鎖的請求

2. Condition對象

內置的條件隊列有一些缺陷,每一個內置鎖都只能由一個相關聯的條件隊列。如果想要編寫一個帶有多個條件謂語的并發對象,可以使用Lock和Condition。

一個Condition和一個單獨的Lock相關聯, 調用Lock.newCondition()方法, 可以創建一個Condition。每個Lock可以有任意數量的Condition對象. wait, notify, notifyAll在Condition中都有對應的:await, signal, signalAll, 而且一定要使用后者!

示例:

public class ConditionBoundedBuffer<T> {  
    private static final int BUFFER_SIZE = 2;  
    private final Lock lock = new ReentrantLock();  
    private final Condition notFull = lock.newCondition();  
    private final Condition notEmpty = lock.newCondition();  
    private final T[] items = (T[]) new Object[BUFFER_SIZE];  
    private int tail, head, count;  

    public void put(T x) throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == items.length) {  
                notFull.await();  //
            }  
            items[tail] = x;  
            if (++tail == items.length) {  
                tail = 0;  
            }  
            count++;  
            notEmpty.signal();  //
        } finally {  
            lock.unlock();  
        }  
    }  

    public T take() throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == 0) {  
                notEmpty.await();// 
            }  
            T x = items[head];  
            items[head] = null;  
            if (++head == items.length) {  
                head = 0;  
            }  
            count--;  
            notFull.signal();  //
            return x;  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

3.AbstractQueuedSynchronizer(AQS)

同步的實現都是基于AbstractQueuedSynchronizer(AQS),AQS是一個用于構建鎖和同步器的框架,例如ReentrantLock,Semaphore,CountDownLatch等都是基于AQS構建的。

AQS構建的容器中,最基本的就是獲取操作和釋放操作,對于CountDownLatch,獲取意味著等待并直到閉鎖到達結束狀態,對于FutureTask,獲取意味著等待直到任務已經完成。

AQS負責同步容器類中的狀態,它管理了一個整數狀態信息,可以通過getState,setState以及compareAndSetState來設置和獲取。例如ReentrantLock用它來表示線程已經重復獲取該鎖的次數,Semaphore用它來表示剩余的許可數量,FutureTask用它來表示任務的狀態(尚未開始,正在運行,已完成以及以取消)

AQS獲取操作可能是獨占的, 就像ReentrantLock一樣, 也可能是非獨占的, 就像Semaphore和CountDownLatch一樣, 這取決于不同的Synchronizer

下面使用AQS來實現一個閉鎖:(事實上,同步容器就是這樣做的)

public class OneShotLatch {  
    private final Sync sync = new Sync();  

    private class Sync extends AbstractQueuedSynchronizer {  
        @Override  
        protected int tryAcquireShared(int arg) {  
            return (getState() == 1) ? 1 : -1;  
        }  

        @Override  
        protected boolean tryReleaseShared(int arg) {  
            setState(1);  
            return true;  
        }  
    }  

    public void signal() {  
        sync.releaseShared(0);  
    }  

    public void await() throws InterruptedException {  
        sync.acquireInterruptibly(0);  
    }  
}

代碼中通過AQS管理閉鎖的狀態:關閉0, 打開1。await方法調用AQS的acqurieSharedInterruptibly, 后者隨后請求OneShotLatch中的tryAcquireShared方法必須返回一個值來表明操作能否繼續執行. 如果閉鎖已經事先打開, tryAcquireShared會返回成功, 并允許線程通過; 否則他會返回一個值, 表明獲取請求的嘗試失敗. acquireSharedInterruptibly方法處理失敗的方式, 是把線程植入一個隊列中, 該隊列中的元素都是等待中的線程, signal調用releaseShared, 進而導致tryReleaseShared被調用. tryReleasseShared的實現無條件地把閉鎖的狀態設置為打開, 通過返回值表明Synchronizer處于完全釋放的狀態

4.java.util.concurrent同步類中的AQS

java.util.concurrent同步類都沒有直接擴展AQS,而是將他們的功能委托給AQS子類實現。
java.util.concurrent中基于AQS開發的類有:ReentrantLock,Semaphore,ReentrantReadWriteLock,CountDownLatch,SynchronousQueue和FutureTask等。

二、原子變量和非阻塞同步機制

非阻塞算法被廣泛用于操作系統和JVM中實現線程/進程調度機制,垃圾回收機制以及鎖和其它并發數據結構。

1. 比較并交換(CAS)

概念:
1. 比較并交換有三個操作數: 內存位置V,進行比較的值A,新值B
2. 當且僅當V的值等于舊值A時, CAS才會用新值B原子化地更新V的值, 否則它什么都不會做。
3. 無論位置V的值是否等于A,CAS都會返回V的真實值。CAS的意思是: 我認為V的值應該是A, 如果是, 那么將B值賦值給V, 若不是, 則不修改, 并告訴我V的舊值為多少。
4. CAS是一項樂觀技術: 它抱著成功的希望進行更新, 并且如果另一個線程在上次檢查后更新了該變量, 它能夠發現錯誤
5. 當多個線程試圖使用CAS同時更新相同的變量時, 只有一個線程會更新變量的值,而其他的都會失敗。然而,失敗的線程不會被掛起, 他們會被告知這次賽跑失利, 但是允許重試。由于一個線程不會競爭CAS時不會被阻塞,因此它可以決定是否重試。

CAS的典型使用模式:
首先從V中讀取值A,根據A計算值B,然后通過CAS以原子操作將V的值A變為B。

一個模擬的CAS:

public class SimulateCAS {  
    private int value;  

    public synchronized int get() {  
        return value;  
    }  

    public synchronized int compareAndSwap(int expectedValue, int newValue) {  
        int oldValue = value;  
        if (expectedValue == oldValue) {  
            value = newValue;  
        }  
        return oldValue;  //無論能否修改,都返回舊值
    }  

    public synchronized boolean compareAndSet(int expectedValue, int newValue) {  
        return (expectedValue == compareAndSwap(expectedValue, newValue));  
    }  
}

基于CAS實現的線程安全的計數器:

public class CasCounter {  
    private SimulateCAS value = new SimulateCAS();  

    public int getValue() {  
        return value.get();  
    }  

    public int increment() {  
        int v;  
        do {  
            v = value.get();  
        } while (v != value.compareAndSwap(v, v + 1));  
        return v + 1;  
    }  
}  

2.非阻塞算法

如果線程在持有鎖時由于阻塞IO,內存頁缺失或其他延遲導致推遲執行,那么很可能所有的線程都不能繼續執行下去。
非阻塞算法:如果在某種算法中,一個線程的失敗或者掛起不會導致其他線程也失敗或掛起,那么這種算法就被稱為非阻塞算法。
無鎖算法:如果在算法的每一個步驟都存在某個線程能夠執行下去,那么這種算法稱為無鎖算法。
無阻塞,無鎖算法:如果算法中僅將CAS用于協調線程之間的操作,并且能夠正確地實現,那么它是一種無阻塞,無鎖算法。

上面基于CAS實現的線程安全的計數器就是無阻塞算法實現

基于非阻塞的Stack實現:

public class ConcurrentStack<E> {  
    class Node<E> {  
        E item;  
        Node<E> next;  
        public Node(E item) {  
            this.item = item;  
        }  

    }  

    AtomicReference<Node<E>> top = new AtomicReference<Node<E>>(); // 對棧頂的一個引用  

    public void push(E item) {  
        Node<E> newHead = new Node<E>(item);  
        Node<E> oldHead;  
        do {  
            oldHead = top.get();  
            newHead.next = oldHead;  
        } while (!top.compareAndSet(oldHead, newHead));  
    }  

    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;  
    }  
}

基于非阻塞的鏈表:

public class LinkedQueue<E> {  
    static class Node<E> {  

        final E item;  
        final AtomicReference<Node<E>> next;  

        public Node() {  
            this(null, null);  
        }  

        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>();  
    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> curTailNode = tail.get();  
            Node<E> tailNextNode = curTailNode.next.get();  
            if (curTailNode == tail.get()) {  
                if (tailNextNode == null) {  
                    // 更新尾節點下一個節點  
                    if (curTailNode.next.compareAndSet(null, newNode)) {  
                        // 更新成功, 將尾節點指向下一個節點  
                        tail.compareAndSet(curTailNode, newNode);  
                        return true;  
                    }  
                } else {  
                    // 在更新過程中, 發現尾節點的下一個節點被更新了, 將尾節點指向下一個節點  
                    tail.compareAndSet(curTailNode, tailNextNode);  
                }  
            }  
        }  
    }  

    public static void main(String[] args) {  
        final LinkedQueue<String> queue = new LinkedQueue<String>();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                queue.put("item1");  
            }  
        }).start();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                queue.put("item2");  
            }  
        }).start();  
    }  
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容