io.github.resilience4j.circularbuffer.ConcurrentEvictingQueue Maven / Gradle / Ivy
Show all versions of resilience4j-circularbuffer Show documentation
/*
*
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
*
* 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 io.github.resilience4j.circularbuffer;
import java.util.*;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Supplier;
import static java.lang.reflect.Array.newInstance;
import static java.util.Objects.requireNonNull;
/**
* The purpose of this queue is to store the N most recently inserted elements. If the {@link
* ConcurrentEvictingQueue} is already full {@code ConcurrentEvictingQueue.size() == capacity}, the
* oldest element (the head) will be evicted, and then the new element added at the tail.
*
* In order to achieve thread-safety it utilizes capability-based locking features of {@link
* StampedLock}. All spins optimistic/pessimistic reads and writes are encapsulated in following
* methods:
*
*
* - {@link ConcurrentEvictingQueue#readConcurrently(Supplier)}
* - {@link ConcurrentEvictingQueue#readConcurrentlyWithoutSpin(Supplier)}
* - {@link ConcurrentEvictingQueue#writeConcurrently(Supplier)}
*
*
* All other logic just relies on this utility methods.
*
* Also please take into account that {@link ConcurrentEvictingQueue#size}
* and {@link ConcurrentEvictingQueue#modificationsCount} are {@code volatile} fields,
* so we can read them and compare against them without any additional synchronizations.
*
* This class IS thread-safe, and does NOT accept null elements.
*/
public class ConcurrentEvictingQueue extends AbstractQueue {
private static final String ILLEGAL_CAPACITY = "Capacity must be bigger than 0";
private static final String ILLEGAL_ELEMENT = "Element must not be null";
private static final String ILLEGAL_DESTINATION_ARRAY = "Destination array must not be null";
private static final Object[] DEFAULT_DESTINATION = new Object[0];
private static final int RETRIES = 5;
private final int maxSize;
private final StampedLock stampedLock;
private volatile int size;
private Object[] ringBuffer;
private int headIndex;
private int tailIndex;
private int modificationsCount;
public ConcurrentEvictingQueue(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException(ILLEGAL_CAPACITY);
}
maxSize = capacity;
ringBuffer = new Object[capacity];
size = 0;
headIndex = 0;
tailIndex = 0;
modificationsCount = 0;
stampedLock = new StampedLock();
}
/**
* Returns an iterator over the elements in this queue in proper sequence. The elements will be
* returned in order from first (head) to last (tail).
*
* This iterator implementation NOT allow removes and co-modifications.
*
* @return an iterator over the elements in this queue in proper sequence
*/
@Override
public Iterator iterator() {
return readConcurrently(() -> new Iter(headIndex, modificationsCount));
}
/**
* Returns the number of elements in this queue.
*
* @return the number of elements in this queue
*/
@Override
public int size() {
return size;
}
/**
* Inserts the specified element at the tail of this queue if it is possible to do so
* immediately or if capacity limit is exited the oldest element (the head) will be evicted, and
* then the new element added at the tail. This method is generally preferable to method {@link
* #add}, which can fail to insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
@Override
public boolean offer(final E e) {
requireNonNull(e, ILLEGAL_ELEMENT);
Supplier offerElement = () -> {
if (size == 0) {
ringBuffer[tailIndex] = e;
modificationsCount++;
size++;
} else if (size == maxSize) {
headIndex = nextIndex(headIndex);
tailIndex = nextIndex(tailIndex);
ringBuffer[tailIndex] = e;
modificationsCount++;
} else {
tailIndex = nextIndex(tailIndex);
ringBuffer[tailIndex] = e;
size++;
modificationsCount++;
}
return true;
};
return writeConcurrently(offerElement);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public E poll() {
Supplier pollElement = () -> {
if (size == 0) {
return null;
}
E result = (E) ringBuffer[headIndex];
ringBuffer[headIndex] = null;
if (size != 1) {
headIndex = nextIndex(headIndex);
}
size--;
modificationsCount++;
return result;
};
return writeConcurrently(pollElement);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public E peek() {
return readConcurrently(() -> {
if (size == 0) {
return null;
}
return (E) this.ringBuffer[this.headIndex];
});
}
/**
* Atomically removes all of the elements from this queue. The queue will be empty after this
* call returns.
*/
@Override
public void clear() {
Supplier