All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.util.SizeBoundedQueue Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
package org.jgroups.util;

import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Queue;


/**
 * Blocking FIFO queue bounded by the max number of bytes of all elements. When adding threads are blocked due to
 * capacity constraints, and the application terminates, it is the caller's duty to interrupt all threads.
 * @author Bela Ban
 * @since  4.0.4
 */
public class SizeBoundedQueue {
    protected final Lock                   lock;
    protected final Condition              not_full, not_empty;
    protected final int                    max_size; // max accumulated number of bytes of all elements
    protected final Queue>           queue=new ConcurrentLinkedQueue<>();
    protected int                          count;    // accumulated bytes
    protected int                          waiters;  // threads blocked on add() because the queue is full
    protected boolean                      done;     // when true, the queue cannot be used anymore


    public SizeBoundedQueue(int max_size) {
        this(max_size, new ReentrantLock(true));
    }

    public SizeBoundedQueue(int max_size, final Lock lock) {
        this.lock=lock;
        this.max_size=max_size;
        if(lock == null)
            throw new IllegalArgumentException("lock must not be null");
        not_full=lock.newCondition();
        not_empty=lock.newCondition();
    }


    public void add(T element, int size) throws InterruptedException {
        if(element == null)
            throw new IllegalArgumentException("element cannot be null");
        boolean incremented=false;
        lock.lockInterruptibly();
        try {
            while(!done && max_size - this.count - size < 0) {
                if(!incremented) {
                    incremented=true;
                    waiters++;
                }
                not_full.await(); // queue is full; we need to block
            }
            if(done)
                return;
            queue.add(new El<>(element, size));
            boolean signal=count == 0;
            this.count+=size;
            if(signal)
                not_empty.signalAll();
        }
        finally {
            if(incremented)
                waiters--;
            lock.unlock();
        }
    }

    /** Removes and returns the first element or null if the queue is empty */
    public T remove() {
        lock.lock();
        try {
            if(done || queue.isEmpty())
                return null;
            El el=queue.poll();
            count-=el.size;
            not_full.signalAll();
            return el.el;
        }
        finally {
            lock.unlock();
        }
    }

    /**
     * Removes and adds to collection c as many elements as possible (including waiters) but not exceeding max_bytes.
     * E.g. if we have elements {1000b, 2000b, 4000b} and max_bytes=6000, then only the first 2 elements are removed
     * and the new size is 4000
     * @param c The collection to transfer the removed elements to
     * @param max_bytes The max number of bytes to remove
     * @return The accumulated number of bytes of all removed elements
     */
    public int drainTo(Collection c, final int max_bytes) {
        if(c == null)
            throw new IllegalArgumentException("collection to drain elements to must not be null");
        if(max_bytes <= 0)
            return 0;
        int bytes=0;
        El el;
        boolean at_least_one_removed=false;
        lock.lock();
        try {
            // go as long as there are elements in the queue or pending waiters
            while(!done && ((el=queue.peek()) != null || waiters > 0)) {
                if(el != null) {
                    if(bytes + el.size > max_bytes)
                        break;
                    el=queue.poll();
                    at_least_one_removed=true;
                    count-=el.size;
                    bytes+=el.size;
                    c.add(el.el);
                }
                else { // queue is empty, wait on more elements to be added
                    not_full.signalAll(); // releases the waiters, causing them to add their elements to the queue
                    try {
                        not_empty.await();
                    }
                    catch(InterruptedException e) {
                        break;
                    }
                }
            }
            if(at_least_one_removed)
                not_full.signalAll();
            return bytes;
        }
        finally {
            lock.unlock();
        }
    }

    public void clear(boolean done) {
        lock.lock();
        try {
            this.done=done;
            queue.clear();
            count=0;
            not_full.signalAll();
        }
        finally {
            lock.unlock();
        }
    }


    /** Returns the number of elements in the queue */
    public int     getElements() {return queue.size();}

    /** Returns the accumulated size of all elements in the queue */
    public int     size()        {return count;}
    public boolean isEmpty()     {return count == 0;}
    public int     getWaiters()  {return waiters;}
    public boolean hasWaiters()  {return waiters > 0;}

    public boolean isDone() {
        lock.lock();
        try {
            return done;
        }
        finally {
            lock.unlock();
        }
    }

    /** For testing only - should always be the same as size() */
    public int queueSize() {
        return queue.stream().map(el -> el.size).reduce(0, Integer::sum);
    }

    public String toString() {
        return String.format("%d elements / %d bytes (%d waiters): %s", getElements(), size(), waiters, queue);
    }


    protected static class El {
        protected final T   el;
        protected final int size;

        public El(T el, int size) {
            this.el=el;
            this.size=size;
        }

        public String toString() {
            return String.format("%s (%d bytes)", el, size);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy