简体   繁体   English

线程安全CircularFIFOBlockingQueue

[英]Threadsafe CircularFIFOBlockingQueue

I have to use a ThreadSafe LinkedBlockingQueue which will discard elements from the head if the queue is full. 我必须使用ThreadSafe LinkedBlockingQueue,如果队列已满,它将从头部丢弃元素。

Apache provides a CircularFifoQueue but that does not seems to be thread safe. Apache提供了CircularFifoQueue,但这似乎不是线程安全的。

Now in order to achieve this, I am updating the write method from the class which gets called from multiple threads. 现在,为了实现这一点,我正在更新从多个线程调用的类的write方法。

 void addElement(Element e) throws InterruptedException {
        if(queue.size()==MAX_SIZE)
        {
            queue.remove();
        }
        queue.put(e);
    }

Now internally all these 3 operations of checking size, removing and putting uses locks. 现在在内部检查尺寸,移除和放置锁的所有这三个操作。 But in my method, they are not holding any single lock. 但是按照我的方法,它们没有持有任何单个锁。

Now, my understanding is even though they are using internal locks but still they are releasing those locks at the end of statement. 现在,我的理解是,即使他们使用内部锁,但仍然在语句结尾释放这些锁。 These 3 operations are not atomic. 这3个操作不是原子的。

So there is a chance that 1 threads checks the size and proceeds to remove but then other thread end up inserting the element as now 1 space become available. 因此,有1个线程检查大小并继续删除的机会,但是当1个空间可用时,其他线程最终会插入该元素。

Please confirm if my understanding is correct. 请确认我的理解是否正确。 And is there any better way to achieve this without putting a common lock in my writeElement method. 还有没有在我的writeElement方法中放置通用锁的更好的方法来实现这一点。 I dont want to put any lock which can be avoided, as this write methods gets called from multiple threads. 我不想放任何可以避免的锁,因为这种写方法是从多个线程调用的。

Update : Also, what if I use it like below, is this a right usage of readWrite lock. 更新:另外,如果我像下面这样使用它,这是readWrite锁的正确用法。 In this case, my intention is to synchronize write operation with a lock only if I am removing elements from the queue. 在这种情况下,我的目的是仅在我从队列中删除元素时才将写入操作与锁同步。

static final ReadWriteLock readWriteLockOnQueue = new ReentrantReadWriteLock();

 void addElement(Element e) throws InterruptedException {
        if(queue.size()==MAX_SIZE)
        {
            readWriteLockOnQueue.writeLock().lock();
            queue.remove();
            readWriteLockOnQueue.writeLock().unlock();
        }
        readWriteLockOnQueue.readLock().lock();
        queue.put(e);
        readWriteLockOnQueue.readLock().unlock();
    }

If you really proved that the performance issue is caused by blocking and context switches than you can implement a non-blocking version of CircularQueue by yourself. 如果您确实证明性能问题是由阻塞和上下文切换引起的,那么您可以自己实现无阻塞版本的CircularQueue

You can take a look at Apache's implementation and what you have to change to achieve non-blocking thread-safe behavior is that you will have to keep the state of the queue in a single primitive variable ( int , long , or what you think is convenient) and then CAS it. 您可以看一下Apache的实现,为实现非阻塞线程安全行为必须进行的更改是必须将队列状态保留在单个原始变量( intlong或您认为是的变量)中。方便),然后将其CAS。 So here is how your non-blocking queue may look like: 因此,您的非阻塞队列如下所示:

public class CircularQueue<E> {
    private final short maxSize;
    private final AtomicLong ctl;
    private final E[] elements;

    public CircularQueue(short maxSize){
        if(maxSize <= 0) throw new IllegalArgumentException();
        this.maxSize = maxSize;
        ctl = new AtomicLong(); //initial state, end = 0, start = 0, full = false, update in progress = false
        elements = (E[]) new Object[maxSize];
    }

    public boolean addElement(E e){
        final AtomicLong queueState = this.ctl;

        boolean stateUpdated = false;
        long insertIdx = 0;
        long ctl = 0;
        while(!stateUpdated) {
            long currentState = queueState.get();
            if ((currentState & FULL_BIT) != 0) {
                return false;
            } else {
                insertIdx = (short) ((currentState & END_MASK) >>> END_SHIFT);
                long newInsertIdx = insertIdx + 1;
                if(newInsertIdx == maxSize)
                    newInsertIdx = 0;
                if(newInsertIdx == (currentState & START_MASK))
                    ctl = ctlOfNewEndAndFull(currentState, newInsertIdx, true);
                else
                    ctl = ctlOfNewEndAndFull(currentState, newInsertIdx, false);

                stateUpdated = queueState.compareAndSet(currentState, ctl);
            }
        }

        elements[(int) insertIdx] = e;

        long mru = ((ctl & MOST_RECENT_UPD_ELEMENT_INDEX_MASK) >>> MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT);

        while(((mru + 1) % maxSize) != insertIdx && mru != insertIdx){
            ctl = queueState.get();
            mru = (ctl & MOST_RECENT_UPD_ELEMENT_INDEX_MASK) >>> MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT;
        }

        while(!queueState.compareAndSet(ctl, setMru(ctl, insertIdx))){
            ctl = queueState.get();
        }

        return true;
    }

    public E peek(){
        final AtomicLong queueState = this.ctl;

        long ctl = queueState.get();
        long start = ctl & START_MASK;
        long end = (ctl & END_MASK) >>> END_SHIFT;
        boolean full = (ctl & FULL_BIT) != 0;

        while(true) {
            if (start == end & !full)
                return null;
            else if (queueState.compareAndSet(ctl, peekElemStateUpdate(ctl, (start + 1) % maxSize))){
                return elements[(int) start];
            } else {
                ctl = queueState.get();
            }
        }
    }

    public static final long END_MASK = 0xFFFF00000000L; //short
    public static final long END_SHIFT = 32;

    public static final long MOST_RECENT_UPD_ELEMENT_INDEX_MASK = 0xFFFF0000L; //short
    public static final long MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT = 16;

    public static final long START_MASK = 0xFFFFL; //short

    public static final long FULL_BIT = 1L << 63;

    private static long peekElemStateUpdate(long oldState, long start){
        return oldState & ~(START_MASK | FULL_BIT) | start;
    }

    private static long setMru(long oldState, long mru){
        return (oldState & ~MOST_RECENT_UPD_ELEMENT_INDEX_MASK) | (mru << MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT);
    }

    private static long ctlOfNewEndAndFull(long oldState, long newEnd, boolean full){
        if(!full)
            return (oldState & ~(END_MASK | FULL_BIT)) | (newEnd << END_SHIFT);
        else
            return (oldState & ~(END_MASK | FULL_BIT)) | (newEnd << END_SHIFT | FULL_BIT);
    }
}

I did not measure the actual performance under heavy contention and you may have to tune it for your particular use case. 我没有衡量激烈争用下的实际性能,您可能需要针对特定​​用例进行调整。 Also I would advice you to check if it contains races (As far as I can see it does not). 我也建议您检查它是否包含种族(据我所知不包含种族)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM