package com.limegroup.gnutella.util; import java.util.List; import java.util.LinkedList; /** * A queue of items to be processed. * * The queue processes the items in a seperate thread, allowing * the thread to be released when all items are processed. * * Runnables are processed sequentially, one after the other. */ public class ProcessingQueue implements ThreadPool { /** * The list of items to be processed. */ private final List QUEUE = new LinkedList(); /** * The name of the thread to be created. */ private final String NAME; /** * Whether or not the constructed thread should be a managed thread or * not. */ private final boolean MANAGED; /** * The priority at which to run this thread */ private final int PRIORITY; /** * The thread doing the processing. */ private Thread _runner = null; /** * Constructs a new ProcessingQueue using a ManagedThread. */ public ProcessingQueue(String name) { this(name, true); } /** * Constructs a new ProcessingQueue of the given name. If managed * is true, uses a ManagedThread for processing. Otherwise uses * a normal thread. The constructed thread has normal priority */ public ProcessingQueue(String name, boolean managed) { this(name,managed,Thread.NORM_PRIORITY); } /** * Constructs a new ProcessingQueue of the given name. If managed * is true, uses a ManagedThread for processing. Otherwise uses * a normal thread. * @param priority the priority of the processing thread */ public ProcessingQueue(String name, boolean managed, int priority) { NAME = name; MANAGED = managed; PRIORITY = priority; } /** * Add the specified runnable to be processed. */ public synchronized void add(Runnable r) { QUEUE.add(r); notifyAndStart(); } protected synchronized void notifyAndStart() { notify(); if(_runner == null) startRunner(); } /** * Clears all pending items that haven't been processed yet. */ public synchronized void clear() { QUEUE.clear(); } public synchronized int size() { return QUEUE.size(); } /** * Adds the specified runnable to be processed. */ public synchronized void invokeLater(Runnable r) { add(r); } /** * Starts a new runner. */ private synchronized void startRunner() { if(MANAGED) _runner = new ManagedThread(new Processor(), NAME); else _runner = new Thread(new Processor(), NAME); _runner.setPriority(PRIORITY); _runner.setDaemon(true); _runner.start(); } /** * Gets the next item to be processed. */ protected synchronized Runnable next() { if(QUEUE.size() > 0) return (Runnable)QUEUE.remove(0); else return null; } protected synchronized boolean moreTasks() { return !QUEUE.isEmpty(); } /** * The runnable that processes the queue. */ private class Processor implements Runnable { public void run() { try { while(true) { Runnable next = next(); if(next != null) next.run(); // Ideally this would be in a finally clause -- but if it // is then we can potentially ignore exceptions that were // thrown. synchronized(ProcessingQueue.this) { // If something was added before we grabbed the lock, // process those items immediately instead of waiting if(moreTasks()) continue; // Wait a little bit to see if something new is going // to come in, so we don't needlessly kill/recreate // threads. try { ProcessingQueue.this.wait(5 * 1000); } catch(InterruptedException ignored) {} // If something was added and notified us, process it // instead of exiting. if(moreTasks()) continue; // Otherwise, exit else break; } } } finally { // We must restart a new runner if something was added. // It's highly unlikely that something was added between // the try of one synchronized block & the finally of another, // but it technically is possible. // We cannot loop here because we'd lose any exceptions // that may have been thrown. synchronized(ProcessingQueue.this) { if(moreTasks()) startRunner(); else _runner = null; } } } } }