自定义阻塞队列锁定问题

时间:2013-05-19 17:37:51

标签: java android locking queue blocking

我正在尝试使用固定长度的字节数组数组来执行阻塞队列的自定义实现。我没有删除轮询元素,因此我调整了put方法返回字节数组,以便可以直接写入(生成器线程使用MappedByteBuffer直接写入此字节数组)。我添加了“commitPut()”方法来简单地增加计数器并设置“长度”数组。 (如果多个线程会写,这可能是并发问题,但我知道只有一个线程正在编写)。

以下是我目前的情况。它是有效的,如果我一步一步调试,但如果我“运行”它看起来遇到一些锁定问题。我复制,剥离并调整了ArrayBlockingQueue代码。有更好的知识的人可以看一下课程并告诉我我做错了什么,或者如何做得更好(比如直接写缓冲区并在同一步骤设置长度数组和计数器)?

public class ByteArrayBlockingQueue {

    private final int[] lens; // array to valid lengths
    private final byte[][] items; // array of byte arrays

    private int takeIndex = 0;
    private int putIndex = 0;
    private int count = 0;

    public volatile int polledLen = 0; // lenght of last polled byte array

    private final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;

    final int inc(int i) {
        return (++i == items.length)? 0 : i;
    }

    public ByteArrayBlockingQueue(int capacity, int size, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new byte[capacity][size];
        this.lens = new int[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull  = lock.newCondition();
    }

    public byte[] put() throws InterruptedException {
        final byte[][] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            try {
                while (count == items.length)
                    notFull.await();

            } catch (InterruptedException ie) {
                notFull.signal(); // propagate to non-interrupted thread
                throw ie;
            }
            //insert(e, len);
            return items[putIndex];
        } finally {
            lock.unlock();
        }
    }

    public void commitPut(int lenBuf) throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            lens[putIndex] = lenBuf;
            putIndex = inc(putIndex);
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public byte[] poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == 0)
                return null;
            final byte[][] items = this.items;
            final int[] lens = this.lens;
            byte[] e = items[takeIndex];
            this.polledLen = lens[takeIndex];
            //items[takeIndex] = null;
            takeIndex = inc(takeIndex);
            --count;
            notFull.signal();
            return e;

        } finally {
            lock.unlock();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

如果队列环绕,则在消费者读取之前,字节数组可能会被重用和覆盖。简而言之,您需要使用commitGet方法来确保生产者在使用新数据覆盖数组之前等待消费者。

但是,我的建议是你依靠java.util.concurrent.BlockingQueue拥有第二个队列将它们从消费者返回给制作者,并依靠java.nio.ByteByffer来跟踪长度。制作人将按如下方式进行:

ByteBuffer buffer = bufferQueue.poll(); // instead of your put()
buffer.put(source);                     // fill buffer from source MappedByteBuffer
buffer.flip();                          // set length to the amount written
dataQueue.offer(buffer);                // instead of commitPut()

消费者会:

ByteBuffer buffer = dataQueue.poll();   // instead of your get()
buffer.get(...);                        // use data
buffer.clear();                         // reset length               
bufferQueue.offer(buffer);              // this is the missing commitGet()

您最初应在capacity中插入freeQueue个元素。但请注意,这仍然会将数据从source缓冲区复制一次到队列中的临时缓冲区,就像原始代码已经完成的那样。

如果你真的不想复制数据(并确保在所有消费者都阅读之前源不会改变!),你最好的选择是使用一个阻塞队列并插入用{{3获得的缓冲区从您的源缓冲区中为每个数据块传递给消费者。这些将被垃圾收集,但应该比字节数组本身少得多。