线程安全的无锁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是线程安全的,而且是无锁的,具备较高的性能。并发

相关文章
相关标签/搜索