2

I am using an ArrayBlockingQueue but sometimes it gets to full and prevents other objects to be added to it.

What I would like to do is to remove the oldest object in the queue before adding another one when the ArrayBlockingQueue gets full. I need the ArrayBlockingQueue to be like the Guava EvictingQueue but thread safe. I intend to extend the ArrayBlockingQueue and override the offer(E e) method like below:

public class MyArrayBlockingQueue<E> extends ArrayBlockingQueue<E> {

// Size of the queue
private int size;

// Constructor
public MyArrayBlockingQueue(int queueSize) {

    super(queueSize);
    this.size = queueSize;
}

@Override
synchronized public boolean offer(E e) {

    // Is queue full?
    if (super.size() == this.size) {
        // if queue is full remove element 
        this.remove();
    }
    return super.offer(e);
} }

Is the above approach OK? Or is there a better way of doing it?

Thanks

user1982350
  • 2,441
  • 2
  • 14
  • 11

2 Answers2

1

Your MyArrayBlockingQueue doesn't override BlockingQueue.offer(E, long, TimeUnit) or BlockingQueue.poll(long, TImeUnit). Do you actually need a queue with "blocking" features? If you do not then you can create a thread-safe queue backed by an EvictingQueue using Queues.synchronizedQueue(Queue):

Queues.synchronizedQueue(EvictingQueue.create(maxSize));

For an evicting blocking queue, I see a few issues with your proposed implementation:

  1. remove() may throw an exception if the queue is empty. Your offer method is marked with synchronized but poll, remove, etc. are not so another thread could drain your queue in between calls to size() and remove(). I suggest using poll() instead which won't throw an exception.
  2. Your call to offer may still return false (i.e. not "add" the element) because of another race condition where between checking the size and/or removing an element to reduce the size a different thread adds an element filling the queue. I recommend using a loop off of the result of offer until true is returned (see below).
  3. Calling size(), remove() and offer(E) each require a lock so in the worse case scenario your code locks and unlocks 3 times (and even then it might fail to behave as desired due to the previous issues described).

I believe the following implementation will get you what you are after:

public class EvictingBlockingQueue<E> extends ArrayBlockingQueue<E> {
    public EvictingBlockingQueue(int capacity) {
        super(capacity);
    }

    @Override
    public boolean offer(E e) {
        while (!super.offer(e)) poll();
        return true;
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        while (!super.offer(e, timeout, unit)) poll();
        return true;
    }
}

Note that this implementation can unnecessarily remove an element if between two calls to super.offer(E) another thread removes an element. This seems acceptable to me and I don't really see a practical way around it (ArrayBlockingQueue.lock is package-private and java.util.concurrent is a prohibited package so we can't place an implementation there to access and use the lock, etc.).

mfulton26
  • 29,956
  • 6
  • 64
  • 88
  • Hi mfulton26. My mistake I still want a queue with blocking features. Are you saying that a Queues.synchronizedQueue(EvictingQueue.create(maxSize)); would have blocking? – user1982350 Dec 08 '16 at 20:08
  • @user1982350 I have updated my answer to address the need for an evicting blocking queue. – mfulton26 Dec 08 '16 at 21:46
  • Thanks mfulton26. I will give it a try. I find it surprising that no one has written a BlockingQueue that automatically removes object from itself after a certain amount of time. Ideally I just wanted an ArrayBlockingQueue that keeps track of how long an object has been in the queue. Once a certain time has elapsed get rid of the old objects. I will try your solution. – user1982350 Dec 09 '16 at 00:50
0

When you say "it gets to full and prevents other objects to be added", does that mean it would be sufficient to ensure that objects can be added anytime? If that's true, you could simply switch to an unbounded queue such as LinkedBlockingQueue. But be aware of the differences compared with ArrayBlockingQueue:

Linked queues typically have higher throughput than array-based queues but less predictable performance in most concurrent applications.

You can find an overview of JDK queue implementations here.

beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • I thought of that but I am trying also to avoid an object to stay in the queue for too long. That is why I would like to remove the object at the head of the queue to make room for the next message to be added on the tail. I need to evict a message when the queue is full and retain the blocking of the ArrayBlockingQueue. It seems that Guava EvictingQueue might do the job. – user1982350 Dec 08 '16 at 20:26