/*
GNU GENERAL LICENSE
Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
verion 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General License for more details.
You should have received a copy of the GNU General Public
along with this program. If not, see <http://www.gnu.org/licenses/>.
Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
*/
package org.lobobrowser.util;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* A thread pool that allows cancelling all running tasks without shutting down
* the thread pool.
*/
public class SimpleThreadPool {
/** The Constant logger. */
private static final Logger logger = LogManager
.getLogger(SimpleThreadPool.class);
/** The task list. */
private final LinkedList<SimpleThreadPoolTask> taskList = new LinkedList<SimpleThreadPoolTask>();
/** The running set. */
private final Set<SimpleThreadPoolTask> runningSet = new HashSet<SimpleThreadPoolTask>();
/** The min threads. */
private final int minThreads;
/** The max threads. */
private final int maxThreads;
/** The name. */
private final String name;
/** The idle alive millis. */
private final int idleAliveMillis;
/** The task monitor. */
private final Object taskMonitor = new Object();
/** The thread group. */
private final ThreadGroup threadGroup;
/** The num threads. */
private int numThreads = 0;
/** The num idle threads. */
private int numIdleThreads = 0;
/** The thread number. */
private int threadNumber = 0;
/**
* Instantiates a new simple thread pool.
*
* @param name
* the name
* @param minShrinkToThreads
* the min shrink to threads
* @param maxThreads
* the max threads
* @param idleAliveMillis
* the idle alive millis
*/
public SimpleThreadPool(String name, int minShrinkToThreads, int maxThreads,
int idleAliveMillis) {
this.minThreads = minShrinkToThreads;
this.maxThreads = maxThreads;
this.idleAliveMillis = idleAliveMillis;
this.name = name;
// Thread group needed so item requests
// don't get assigned sub-thread groups.
// TODO: Thread group needs to be thought through. It's retained in
// memory, and we need to return the right one in the GUI thread as
// well.
this.threadGroup = null; // new ThreadGroup(name);
}
/**
* Schedule.
*
* @param task
* the task
*/
public void schedule(SimpleThreadPoolTask task) {
if (task == null) {
throw new IllegalArgumentException("null task");
}
Object monitor = this.taskMonitor;
synchronized (monitor) {
if (this.numIdleThreads == 0) {
this.addThreadImpl();
}
this.taskList.add(task);
monitor.notify();
}
}
/**
* Cancel.
*
* @param task
* the task
*/
public void cancel(SimpleThreadPoolTask task) {
synchronized (this.taskMonitor) {
this.taskList.remove(task);
}
task.cancel();
}
/**
* Adds the thread impl.
*/
private void addThreadImpl() {
if (this.numThreads < this.maxThreads) {
Thread t = new Thread(this.threadGroup, new ThreadRunnable(),
this.name + this.threadNumber++);
t.setDaemon(true);
t.start();
this.numThreads++;
}
}
/**
* Cancels all waiting tasks and any currently running task.
*/
public void cancelAll() {
synchronized (this.taskMonitor) {
this.taskList.clear();
Iterator i = this.runningSet.iterator();
while (i.hasNext()) {
((SimpleThreadPoolTask) i.next()).cancel();
}
}
}
/**
* The Class ThreadRunnable.
*/
private class ThreadRunnable implements Runnable {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
Object monitor = taskMonitor;
LinkedList tl = taskList;
Set<SimpleThreadPoolTask> rs = runningSet;
int iam = idleAliveMillis;
SimpleThreadPoolTask task = null;
for (;;) {
try {
synchronized (monitor) {
if (task != null) {
rs.remove(task);
}
numIdleThreads++;
try {
long waitBase = System.currentTimeMillis();
INNER: while (tl.isEmpty()) {
long maxWait = iam - (System.currentTimeMillis()
- waitBase);
if (maxWait <= 0) {
if (numThreads > minThreads) {
// Should be only way to exit thread.
numThreads--;
return;
} else {
waitBase = System.currentTimeMillis();
continue INNER;
}
}
monitor.wait(maxWait);
}
} finally {
numIdleThreads--;
}
task = (SimpleThreadPoolTask) taskList.removeFirst();
rs.add(task);
}
Thread currentThread = Thread.currentThread();
String baseName = currentThread.getName();
try {
try {
currentThread
.setName(baseName + ":" + task.toString());
} catch (Throwable thrown) {
logger.error("run(): Unable to set task name.", thrown);
}
try {
task.run();
} catch (Throwable thrown) {
logger.log(Level.ERROR,
"run(): Error in task: " + task + ".",
thrown);
}
} finally {
currentThread.setName(baseName);
}
} catch (Throwable thrown) {
logger.log(Level.ERROR, "run(): Error in thread pool: "
+ SimpleThreadPool.this.name + ".", thrown);
}
}
}
}
}