/** * Copyright 2016 Yahoo Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.yahoo.pulsar.common.util.collections; import java.util.AbstractQueue; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import io.netty.util.internal.MathUtil; /** * This implements a {@link BlockingQueue} backed by an array with no fixed capacity. * * When the capacity is reached, data will be moved to a bigger array. * */ public class GrowableArrayBlockingQueue<T> extends AbstractQueue<T> implements BlockingQueue<T> { private final ReentrantLock headLock = new ReentrantLock(); private final PaddedInt headIndex = new PaddedInt(); private final PaddedInt tailIndex = new PaddedInt(); private final ReentrantLock tailLock = new ReentrantLock(); private final Condition isNotEmpty = headLock.newCondition(); private T[] data; private static final AtomicIntegerFieldUpdater<GrowableArrayBlockingQueue> SIZE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(GrowableArrayBlockingQueue.class, "size"); private volatile int size = 0; public GrowableArrayBlockingQueue() { this(64); } @SuppressWarnings("unchecked") public GrowableArrayBlockingQueue(int initialCapacity) { headIndex.value = 0; tailIndex.value = 0; int capacity = MathUtil.findNextPositivePowerOfTwo(initialCapacity); data = (T[]) new Object[capacity]; } @Override public T remove() { T item = poll(); if (item == null) { throw new NoSuchElementException(); } return item; } @Override public T poll() { headLock.lock(); try { if (SIZE_UPDATER.get(this) > 0) { T item = data[headIndex.value]; headIndex.value = (headIndex.value + 1) & (data.length - 1); SIZE_UPDATER.decrementAndGet(this); return item; } else { return null; } } finally { headLock.unlock(); } } @Override public T element() { T item = peek(); if (item == null) { throw new NoSuchElementException(); } return item; } @Override public T peek() { headLock.lock(); try { if (SIZE_UPDATER.get(this) > 0) { return data[headIndex.value]; } else { return null; } } finally { headLock.unlock(); } } @Override public boolean offer(T e) { // Queue is unbounded and it will never reject new items put(e); return true; } @Override public void put(T e) { tailLock.lock(); boolean wasEmpty = false; try { if (SIZE_UPDATER.get(this) == data.length) { expandArray(); } data[tailIndex.value] = e; tailIndex.value = (tailIndex.value + 1) & (data.length - 1); if (SIZE_UPDATER.getAndIncrement(this) == 0) { wasEmpty = true; } } finally { tailLock.unlock(); } if (wasEmpty) { headLock.lock(); try { isNotEmpty.signal(); } finally { headLock.unlock(); } } } @Override public boolean add(T e) { put(e); return true; } @Override public boolean offer(T e, long timeout, TimeUnit unit) { // Queue is unbounded and it will never reject new items put(e); return true; } @Override public T take() throws InterruptedException { headLock.lockInterruptibly(); try { while (SIZE_UPDATER.get(this) == 0) { isNotEmpty.await(); } T item = data[headIndex.value]; data[headIndex.value] = null; headIndex.value = (headIndex.value + 1) & (data.length - 1); if (SIZE_UPDATER.decrementAndGet(this) > 0) { // There are still entries to consume isNotEmpty.signal(); } return item; } finally { headLock.unlock(); } } @Override public T poll(long timeout, TimeUnit unit) throws InterruptedException { headLock.lockInterruptibly(); try { long timeoutNanos = unit.toNanos(timeout); while (SIZE_UPDATER.get(this) == 0) { if (timeoutNanos <= 0) { return null; } timeoutNanos = isNotEmpty.awaitNanos(timeoutNanos); } T item = data[headIndex.value]; data[headIndex.value] = null; headIndex.value = (headIndex.value + 1) & (data.length - 1); if (SIZE_UPDATER.decrementAndGet(this) > 0) { // There are still entries to consume isNotEmpty.signal(); } return item; } finally { headLock.unlock(); } } @Override public int remainingCapacity() { return Integer.MAX_VALUE; } @Override public int drainTo(Collection<? super T> c) { return drainTo(c, Integer.MAX_VALUE); } @Override public int drainTo(Collection<? super T> c, int maxElements) { headLock.lock(); try { int drainedItems = 0; int size = SIZE_UPDATER.get(this); while (size > 0 && drainedItems < maxElements) { T item = data[headIndex.value]; data[headIndex.value] = null; c.add(item); headIndex.value = (headIndex.value + 1) & (data.length - 1); --size; ++drainedItems; } if (SIZE_UPDATER.addAndGet(this, -drainedItems) > 0) { // There are still entries to consume isNotEmpty.signal(); } return drainedItems; } finally { headLock.unlock(); } } @Override public void clear() { headLock.lock(); try { int size = SIZE_UPDATER.get(this); for (int i = 0; i < size; i++) { data[headIndex.value] = null; headIndex.value = (headIndex.value + 1) & (data.length - 1); } if (SIZE_UPDATER.addAndGet(this, -size) > 0) { // There are still entries to consume isNotEmpty.signal(); } } finally { headLock.unlock(); } } @Override public int size() { return SIZE_UPDATER.get(this); } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); tailLock.lock(); headLock.lock(); try { int headIndex = this.headIndex.value; int size = SIZE_UPDATER.get(this); sb.append('['); for (int i = 0; i < size; i++) { T item = data[headIndex]; if (i > 0) { sb.append(", "); } sb.append(item); headIndex = (headIndex + 1) & (data.length - 1); } sb.append(']'); } finally { headLock.unlock(); tailLock.unlock(); } return sb.toString(); } @SuppressWarnings("unchecked") private void expandArray() { // We already hold the tailLock headLock.lock(); try { int size = SIZE_UPDATER.get(this); int newCapacity = data.length * 2; T[] newData = (T[]) new Object[newCapacity]; int oldHeadIndex = headIndex.value; int newTailIndex = 0; for (int i = 0; i < size; i++) { newData[newTailIndex++] = data[oldHeadIndex]; oldHeadIndex = (oldHeadIndex + 1) & (data.length - 1); } data = newData; headIndex.value = 0; tailIndex.value = size; } finally { headLock.unlock(); } } final static class PaddedInt { private int value; // Padding to avoid false sharing public volatile int pi1 = 1; public volatile long p1 = 1L, p2 = 2L, p3 = 3L, p4 = 4L, p5 = 5L, p6 = 6L; public long exposeToAvoidOptimization() { return pi1 + p1 + p2 + p3 + p4 + p5 + p6; } } }