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

com.gemstone.org.jgroups.util.Queue2 Maven / Gradle / Ivy

The newest version!
/** Notice of modification as required by the LGPL
 *  This file was modified by Gemstone Systems Inc. on
 *  $Date$
 **/
// $Id: Queue2.java,v 1.5 2004/12/31 14:10:40 belaban Exp $

package com.gemstone.org.jgroups.util;


import com.gemstone.org.jgroups.oswego.concurrent.CondVar;
import com.gemstone.org.jgroups.oswego.concurrent.Mutex;
import com.gemstone.org.jgroups.oswego.concurrent.Sync;
import com.gemstone.org.jgroups.util.GemFireTracer;
import com.gemstone.org.jgroups.TimeoutException;

import java.util.Vector;




/**
 * Elements are added at the tail and removed from the head. Class is thread-safe in that
 * 1 producer and 1 consumer may add/remove elements concurrently. The class is not
 * explicitely designed for multiple producers or consumers. Implemented as a linked
 * list, so that removal of an element at the head does not cause a right-shift of the
 * remaining elements (as in a Vector-based implementation).
 * 

Implementation is based on util.concurrent.* classes * @author Bela Ban * @author Filip Hanik */ public class Queue2 { /*head and the tail of the list so that we can easily add and remove objects*/ Element head=null, tail=null; /*flag to determine the state of the queue*/ boolean closed=false; /*current size of the queue*/ int size=0; /* Lock object for synchronization. Is notified when element is added */ final Sync mutex=new Mutex(); /** Signals to listeners when an element has been added */ final CondVar add_condvar=new CondVar(mutex); /** Signals to listeners when an element has been removed */ final CondVar remove_condvar=new CondVar(mutex); /*the number of end markers that have been added*/ int num_markers=0; protected static final GemFireTracer log=GemFireTracer.getLog(Queue2.class); /** * if the queue closes during the runtime * an endMarker object is added to the end of the queue to indicate that * the queue will close automatically when the end marker is encountered * This allows for a "soft" close. * @see Queue#close */ private static final Object endMarker=new Object(); /** * the class Element indicates an object in the queue. * This element allows for the linked list algorithm by always holding a * reference to the next element in the list. * if Element.next is null, then this element is the tail of the list. */ static/*GemStoneAddition*/ class Element { /*the actual value stored in the queue*/ Object obj=null; /*pointer to the next item in the (queue) linked list*/ Element next=null; /** * creates an Element object holding its value * @param o - the object to be stored in the queue position */ Element(Object o) { obj=o; } /** * prints out the value of the object */ @Override // GemStoneAddition public String toString() { return obj != null? obj.toString() : "null"; } } /** * creates an empty queue */ public Queue2() { } /** * Returns the first element. Returns null if no elements are available. */ public Object getFirst() { return head != null? head.obj : null; } /** * Returns the last element. Returns null if no elements are available. */ public Object getLast() { return tail != null? tail.obj : null; } /** * returns true if the Queue has been closed * however, this method will return false if the queue has been closed * using the close(true) method and the last element has yet not been received. * @return true if the queue has been closed */ public boolean closed() { return closed; } /** * adds an object to the tail of this queue * If the queue has been closed with close(true) no exception will be * thrown if the queue has not been flushed yet. * @param obj - the object to be added to the queue * @exception QueueClosedException exception if closed() returns true */ public void add(Object obj) throws QueueClosedException { if(obj == null) { if(log.isErrorEnabled()) log.error(ExternalStrings.Queue2_ARGUMENT_MUST_NOT_BE_NULL); return; } if(closed) throw new QueueClosedException(); if(this.num_markers > 0) throw new QueueClosedException("Queue2.add(): queue has been closed. You can not add more elements. " + "Waiting for removal of remaining elements."); try { mutex.acquire(); /*create a new linked list element*/ Element el=new Element(obj); /*check the first element*/ if(head == null) { /*the object added is the first element*/ /*set the head to be this object*/ head=el; /*set the tail to be this object*/ tail=head; /*set the size to be one, since the queue was empty*/ size=1; } else { /*add the object to the end of the linked list*/ tail.next=el; /*set the tail to point to the last element*/ tail=el; /*increase the size*/ size++; } /*wake up all the threads that are waiting for the lock to be released*/ add_condvar.broadcast(); // todo: maybe signal is all we need ? } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } } /** * Adds a new object to the head of the queue * basically (obj.equals(queue.remove(queue.add(obj)))) returns true * If the queue has been closed with close(true) no exception will be * thrown if the queue has not been flushed yet. * @param obj - the object to be added to the queue * @exception QueueClosedException exception if closed() returns true * */ public void addAtHead(Object obj) throws QueueClosedException { if(obj == null) { if(log.isErrorEnabled()) log.error(ExternalStrings.Queue2_ARGUMENT_MUST_NOT_BE_NULL); return; } if(closed) throw new QueueClosedException(); if(this.num_markers > 0) throw new QueueClosedException("Queue2.addAtHead(): queue has been closed. You can not add more elements. " + "Waiting for removal of remaining elements."); try { mutex.acquire(); Element el=new Element(obj); /*check the head element in the list*/ if(head == null) { /*this is the first object, we could have done add(obj) here*/ head=el; tail=head; size=1; } else { /*set the head element to be the child of this one*/ el.next=head; /*set the head to point to the recently added object*/ head=el; /*increase the size*/ size++; } /*wake up all the threads that are waiting for the lock to be released*/ add_condvar.broadcast(); } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } } /** * Removes 1 element from head or blocks * until next element has been added or until queue has been closed * @return the first element to be taken of the queue */ public Object remove() throws QueueClosedException { /*initialize the return value*/ Object retval=null; try { mutex.acquire(); /*wait as long as the queue is empty. return when an element is present or queue is closed*/ while(size == 0) { if(closed) throw new QueueClosedException(); boolean interrupted = Thread.interrupted(); try { add_condvar.await(); } catch(InterruptedException ex) { interrupted = true; // GemStoneAddition } finally { // GemStoneAddition if (interrupted) { Thread.currentThread().interrupt(); } } } if(closed) throw new QueueClosedException(); /*remove the head from the queue, if we make it to this point, retval should not be null !*/ retval=removeInternal(); if(retval == null) if(log.isErrorEnabled()) log.error(ExternalStrings.Queue2_ELEMENT_WAS_NULL_SHOULD_NEVER_BE_THE_CASE); } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } /* * we ran into an Endmarker, which means that the queue was closed before * through close(true) */ if(retval == endMarker) { close(false); // mark queue as closed throw new QueueClosedException(); } /*return the object*/ return retval; } /** * Removes 1 element from the head. * If the queue is empty the operation will wait for timeout ms. * if no object is added during the timeout time, a Timout exception is thrown * @param timeout - the number of milli seconds this operation will wait before it times out * @return the first object in the queue */ public Object remove(long timeout) throws QueueClosedException, TimeoutException { Object retval=null; try { mutex.acquire(); /*if the queue size is zero, we want to wait until a new object is added*/ if(size == 0) { if(closed) throw new QueueClosedException(); try { /*release the mutex lock and wait no more than timeout ms*/ add_condvar.timedwait(timeout); } catch(InterruptedException ex) { Thread.currentThread().interrupt(); // GemStoneAddition } } /*we either timed out, or got notified by the mutex lock object*/ /*check to see if the object closed*/ if(closed) throw new QueueClosedException(); /*get the next value*/ retval=removeInternal(); /*null result means we timed out*/ if(retval == null) throw new TimeoutException(); /*if we reached an end marker we are going to close the queue*/ if(retval == endMarker) { close(false); throw new QueueClosedException(); } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } /*at this point we actually did receive a value from the queue, return it*/ return retval; } /** * removes a specific object from the queue. * the object is matched up using the Object.equals method. * @param obj the actual object to be removed from the queue */ public void removeElement(Object obj) throws QueueClosedException { Element el, tmp_el; boolean removed=false; if(obj == null) { if(log.isErrorEnabled()) log.error(ExternalStrings.Queue2_ARGUMENT_MUST_NOT_BE_NULL); return; } try { mutex.acquire(); el=head; /*the queue is empty*/ if(el == null) return; /*check to see if the head element is the one to be removed*/ if(el.obj.equals(obj)) { /*the head element matched we will remove it*/ head=el.next; el.next=null; /*check if we only had one object left *at this time the queue becomes empty *this will set the tail=head=null */ if(size == 1) tail=head; // null decrementSize(); removed=true; /*and end the operation, it was successful*/ return; } /*look through the other elements*/ while(el.next != null) { if(el.next.obj.equals(obj)) { tmp_el=el.next; if(tmp_el == tail) // if it is the last element, move tail one to the left (bela Sept 20 2002) tail=el; el.next=el.next.next; // point to the el past the next one. can be null. tmp_el.next=null; decrementSize(); removed=true; break; } el=el.next; } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { if(removed) remove_condvar.broadcast(); mutex.release(); } } /** * returns the first object on the queue, without removing it. * If the queue is empty this object blocks until the first queue object has * been added * @return the first object on the queue */ public Object peek() throws QueueClosedException { Object retval=null; try { mutex.acquire(); while(size == 0) { if(closed) throw new QueueClosedException(); boolean interrupted = Thread.interrupted(); // GemStoneAddition try { add_condvar.await(); } catch(InterruptedException ex) { interrupted = true; // GemStoneAddition } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } if(closed) throw new QueueClosedException(); retval=(head != null)? head.obj : null; // @remove: if(retval == null) { // print some diagnostics if(log.isErrorEnabled()) log.error("retval is null: head=" + head + ", tail=" + tail + ", size()=" + size() + ", num_markers=" + num_markers + ", closed()=" + closed()); } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } if(retval == endMarker) { close(false); // mark queue as closed throw new QueueClosedException(); } return retval; } /** * returns the first object on the queue, without removing it. * If the queue is empty this object blocks until the first queue object has * been added or the operation times out * @param timeout how long in milli seconds will this operation wait for an object to be added to the queue * before it times out * @return the first object on the queue */ public Object peek(long timeout) throws QueueClosedException, TimeoutException { Object retval=null; try { mutex.acquire(); if(size == 0) { if(closed) throw new QueueClosedException(); try { mutex.wait(timeout); } catch(InterruptedException ex) { Thread.currentThread().interrupt(); // GemStoneAddition } } if(closed) throw new QueueClosedException(); retval=head != null? head.obj : null; if(retval == null) throw new TimeoutException(); if(retval == endMarker) { close(false); throw new QueueClosedException(); } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } return retval; } /** Marks the queues as closed. When an add or remove operation is attempted on a closed queue, an exception is thrown. @param flush_entries When true, a end-of-entries marker is added to the end of the queue. Entries may be added and removed, but when the end-of-entries marker is encountered, the queue is marked as closed. This allows to flush pending messages before closing the queue. */ public void close(boolean flush_entries) { if(flush_entries) { try { add(endMarker); // add an end-of-entries marker to the end of the queue num_markers++; } catch(QueueClosedException closed) { } return; } try { mutex.acquire(); closed=true; try { add_condvar.broadcast(); remove_condvar.broadcast(); } catch(Exception e) { if(log.isErrorEnabled()) log.error(ExternalStrings.Queue2_EXCEPTION_0, e); } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } } /** * resets the queue. * This operation removes all the objects in the queue and marks the queue open */ public void reset() { num_markers=0; if(!closed) close(false); try { mutex.acquire(); size=0; head=null; tail=null; closed=false; } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } } /** * returns the number of objects that are currently in the queue */ public int size() { return size - num_markers; } /** * prints the size of the queue */ @Override // GemStoneAddition public String toString() { return "Queue2 (" + size() + ") messages"; } /** * Dumps internal state @remove */ public String debug() { return toString() + ", head=" + head + ", tail=" + tail + ", closed()=" + closed() + ", contents=" + getContents(); } /** * returns a vector with all the objects currently in the queue */ public Vector getContents() { Vector retval=new Vector(); Element el; try { mutex.acquire(); el=head; while(el != null) { retval.addElement(el.obj); el=el.next; } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } return retval; } /** * Blocks until the queue has no elements left. If the queue is empty, the call will return * immediately * @param timeout Call returns if timeout has elapsed (number of milliseconds). 0 means to wait forever * @throws QueueClosedException Thrown if queue has been closed * @throws TimeoutException Thrown if timeout has elapsed */ public void waitUntilEmpty(long timeout) throws QueueClosedException, TimeoutException { long time_to_wait=timeout >=0? timeout : 0; // eliminate negative values try { mutex.acquire(); if(timeout == 0) { while(size > 0 && closed == false) { remove_condvar.await(); } } else { long start_time=System.currentTimeMillis(); while(time_to_wait > 0 && size > 0 && closed == false) { boolean interrupted = Thread.interrupted(); // GemStoneAddition try { remove_condvar.timedwait(time_to_wait); } catch(InterruptedException ex) { interrupted = true; // GemStoneAddition } finally { // GemStoneAddition if (interrupted) { Thread.currentThread().interrupt(); } } time_to_wait=timeout - (System.currentTimeMillis() - start_time); } if(size > 0) throw new TimeoutException("queue has " + size + " elements"); } if(closed) throw new QueueClosedException(); } catch(InterruptedException e) { Thread.currentThread().interrupt(); // GemStoneAddition } finally { mutex.release(); } } /* ------------------------------------- Private Methods ----------------------------------- */ /** * Removes the first element. Returns null if no elements in queue. * Always called with mutex locked (we don't have to lock mutex ourselves) */ private Object removeInternal() { Element retval; /*if the head is null, the queue is empty*/ if(head == null) return null; retval=head; // head must be non-null now head=head.next; if(head == null) tail=null; decrementSize(); remove_condvar.broadcast(); // todo: correct ? if(head != null && head.obj == endMarker) { closed=true; } retval.next=null; return retval.obj; } void decrementSize() { size--; if(size < 0) size=0; } /* ---------------------------------- End of Private Methods -------------------------------- */ }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy