/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.sun.jini.thread;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A task manager manages a single queue of tasks, and some number of
* worker threads. New tasks are added to the tail of the queue. Each
* thread loops, taking a task from the queue and running it. Each
* thread looks for a task by starting at the head of the queue and
* taking the first task (that is not already being worked on) that is
* not required to run after any of the tasks that precede it in
* the queue (including tasks that are currently being worked on).
* <p>
* This class uses the {@link Logger} named
* <code>com.sun.jini.thread.TaskManager</code> to log information at
* the following logging levels:
* <p>
* <table border=1 cellpadding=5
* summary="Describes logging performed by TaskManager at different
* logging levels">
* <caption halign="center" valign="top"><b><code>
* com.sun.jini.thread.TaskManager</code></b></caption>
* <tr><th>Level<th>Description
* <tr><td>{@link Level#SEVERE SEVERE}<td>
* failure to create a worker thread when no other worker threads exist
* <tr><td>{@link Level#WARNING WARNING}<td>
* exceptions thrown by {@link TaskManager.Task} methods, and failure
* to create a worker thread when other worker threads exist
* </table>
*
* @author Sun Microsystems, Inc.
*
*/
public class TaskManager {
/** The interface that tasks must implement */
public interface Task extends Runnable {
/**
* Return true if this task must be run after at least one task
* in the given task list with an index less than size (size may be
* less then tasks.size()). Using List.get will be more efficient
* than List.iterator.
*
* @param tasks the tasks to consider. A read-only List, with all
* elements instanceof Task.
* @param size elements with index less than size should be considered
*/
boolean runAfter(List tasks, int size);
}
/** Logger */
protected static final Logger logger =
Logger.getLogger("com.sun.jini.thread.TaskManager");
/** Active and pending tasks */
protected final ArrayList tasks = new ArrayList();
/** Index of the first pending task; all earlier tasks are active */
protected int firstPending = 0;
/** Read-only view of tasks */
protected final List roTasks = Collections.unmodifiableList(tasks);
/** Active threads */
protected final List threads = new ArrayList();
/** Maximum number of threads allowed */
protected final int maxThreads;
/** Idle time before a thread should exit */
protected final long timeout;
/** Threshold for creating new threads */
protected final float loadFactor;
/** True if manager has been terminated */
protected boolean terminated = false;
/**
* Create a task manager with maxThreads = 10, timeout = 15 seconds,
* and loadFactor = 3.0.
*/
public TaskManager() {
this(10, 1000 * 15, 3.0f);
}
/**
* Create a task manager.
*
* @param maxThreads maximum number of threads to use on tasks
* @param timeout idle time before a thread exits
* @param loadFactor threshold for creating new threads. A new
* thread is created if the total number of runnable tasks (both active
* and pending) exceeds the number of threads times the loadFactor,
* and the maximum number of threads has not been reached.
*/
public TaskManager(int maxThreads, long timeout, float loadFactor) {
this.maxThreads = maxThreads;
this.timeout = timeout;
this.loadFactor = loadFactor;
}
/**
* Add a new task if it is not equal to (using the equals method)
* to any existing active or pending task.
*/
public synchronized void addIfNew(Task t) {
if (!tasks.contains(t))
add(t);
}
/** Add a new task. */
public synchronized void add(Task t) {
tasks.add(t);
boolean poke = true;
while (threads.size() < maxThreads && needThread()) {
Thread th;
try {
th = new TaskThread();
th.start();
} catch (Throwable tt) {
try {
logger.log(threads.isEmpty() ?
Level.SEVERE : Level.WARNING,
"thread creation exception", tt);
} catch (Throwable ttt) {
}
break;
}
threads.add(th);
poke = false;
}
if (poke &&
threads.size() > firstPending &&
!runAfter(t, tasks.size() - 1))
{
notify();
}
}
/** Add all tasks in a collection, in iterator order. */
public synchronized void addAll(Collection c) {
for (Iterator iter = c.iterator(); iter.hasNext(); ) {
add((Task)iter.next());
}
}
/** Return true if a new thread should be created (ignoring maxThreads). */
protected boolean needThread() {
int bound = (int)(loadFactor * threads.size());
int max = tasks.size();
if (max < bound)
return false;
max--;
if (runAfter((Task)tasks.get(max), max))
return false;
int ready = firstPending + 1;
if (ready > bound)
return true;
for (int i = firstPending; i < max; i++) {
if (!runAfter((Task)tasks.get(i), i)) {
ready++;
if (ready > bound)
return true;
}
}
return false;
}
/**
* Returns t.runAfter(i), or false if an exception is thrown.
*/
private boolean runAfter(Task t, int i) {
try {
return t.runAfter(roTasks, i);
} catch (Throwable tt) {
try {
logger.log(Level.WARNING, "Task.runAfter exception", tt);
} catch (Throwable ttt) {
}
return false;
}
}
/**
* Remove a task if it is pending (not active). Object identity (==)
* is used, not the equals method. Returns true if the task was
* removed.
*/
public synchronized boolean removeIfPending(Task t) {
return removeTask(t, firstPending);
}
/*
* Remove a task if it is pending or active. If it is active and not being
* executed by the calling thread, interrupt the thread executing the task,
* but do not wait for the thread to terminate. Object identity (==) is
* used, not the equals method. Returns true if the task was removed.
*/
public synchronized boolean remove(Task t) {
return removeTask(t, 0);
}
/**
* Remove a task if it has index >= min. If it is active and not being
* executed by the calling thread, interrupt the thread executing the task.
*/
private boolean removeTask(Task t, int min) {
for (int i = tasks.size(); --i >= min; ) {
if (tasks.get(i) == t) {
tasks.remove(i);
if (i < firstPending) {
firstPending--;
for (int j = threads.size(); --j >= 0; ) {
TaskThread thread = (TaskThread)threads.get(j);
if (thread.task == t) {
if (thread != Thread.currentThread())
thread.interrupt();
break;
}
}
}
return true;
}
}
return false;
}
/**
* Interrupt all threads, and stop processing tasks. Only getPending
* should be used afterwards.
*/
public synchronized void terminate() {
terminated = true;
for (int i = threads.size(); --i >= 0; ) {
((Thread)threads.get(i)).interrupt();
}
}
/** Return all pending tasks. A new list is returned each time. */
public synchronized ArrayList getPending() {
ArrayList tc = (ArrayList)tasks.clone();
for (int i = firstPending; --i >= 0; ) {
tc.remove(0);
}
return tc;
}
/** Return the maximum number of threads to use on tasks. */
public int getMaxThreads() {
return maxThreads;
}
private class TaskThread extends Thread {
/** The task being run, if any */
public Task task = null;
public TaskThread() {
super("task");
setDaemon(true);
}
/**
* Find the next task that can be run, and mark it taken by
* moving firstPending past it (and moving the task in front of
* any pending tasks that are skipped due to execution constraints).
* If a task is found, set task to it and return true.
*/
private boolean takeTask() {
int size = tasks.size();
for (int i = firstPending; i < size; i++) {
Task t = (Task)tasks.get(i);
if (!runAfter(t, i)) {
if (i > firstPending) {
tasks.remove(i);
tasks.add(firstPending, t);
}
firstPending++;
task = t;
return true;
}
}
return false;
}
public void run() {
while (true) {
synchronized (TaskManager.this) {
if (terminated)
return;
if (task != null) {
for (int i = firstPending; --i >= 0; ) {
if (tasks.get(i) == task) {
tasks.remove(i);
firstPending--;
break;
}
}
task = null;
interrupted(); // clear interrupt bit
}
if (!takeTask()) {
try {
TaskManager.this.wait(timeout);
} catch (InterruptedException e) {
}
if (terminated || !takeTask()) {
threads.remove(this);
return;
}
}
}
try {
task.run();
} catch (Throwable t) {
try {
logger.log(Level.WARNING, "Task.run exception", t);
} catch (Throwable tt) {
}
}
}
}
}
}