線程安全的無鎖RingBuffer

  RingBuffer是環形緩衝區,支持讀和寫兩種操做,相似於循環隊列。在實現上,通常用數組存儲數據,同時設置雙指針head和tail,head指向隊首,tail指向隊尾。讀數據時,head++;寫數據時,tail++。顯然,RingBuffer不是線程安全的,須要對讀寫數據進行同步。然而,有一種特殊狀況:一個線程讀,一個線程寫,在這種狀況下能夠實現線程安全的無鎖RingBuffer,代碼以下:數組

public class RingBuffer<T> {
    private volatile T[] elements;
    private volatile int head;
    private volatile int tail;

    public RingBuffer(int capacity){
        this.elements=(T[])new Object[capacity];
        this.head=0;
        this.tail=-1;
    }

    private boolean isEmpty(){
        return tail+1==head;
    }

    private boolean isFull(){
        return tail+1-elements.length==head;
    }

    public void push(T element) throws IllegalArgumentException{
        if(isFull())
            throw new IllegalArgumentException("full queue");
        elements[(tail+1)%elements.length]=element;
        tail++;
    }

    public T pop() throws IllegalArgumentException{
        if(isEmpty())
            throw new IllegalArgumentException("empty queue");
        T element=elements[head%elements.length];
        head++;
        return element;
    }
}
複製代碼

  爲何上述代碼可以確保線程安全?能夠看到,建立RingBuffer後,只有寫線程修改tail,只有讀線程修改head,而且始終只有一個讀線程,一個寫線程,所以是沒有併發寫操做的。然而,因爲讀操做和寫操做都不是原子性的,有可能讀操做發生在寫操做的過程當中,寫操做發生在讀操做的過程當中。這樣會致使如下兩個問題:安全

  • 對於讀操做,當RingBuffer爲空時,有可能讀到還沒寫的數據。
  • 對於寫操做,當RingBuffer爲滿時,有可能寫到還沒讀的數據。

可是,對於寫操做,咱們是先修改elements,再修改tail;對於讀操做,咱們是先修改elements,再修改head。所以上述兩個問題是不存在的,咱們說上述RingBuffer是線程安全的,而且是無鎖的,具備較高的性能。併發

相關文章
相關標籤/搜索