package org.lobobrowser.util; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * A thread pool that allows cancelling all running tasks without shutting down * the thread pool. */ public class SimpleThreadPool { private static final Logger logger = Logger.getLogger(SimpleThreadPool.class.getName()); private final LinkedList<SimpleThreadPoolTask> taskList = new LinkedList<>(); private final Set<SimpleThreadPoolTask> runningSet = new HashSet<>(); private final int minThreads; private final int maxThreads; private final String name; private final int idleAliveMillis; private final Object taskMonitor = new Object(); private final ThreadGroup threadGroup; private int numThreads = 0; private int numIdleThreads = 0; private int threadNumber = 0; public SimpleThreadPool(final String name, final int minShrinkToThreads, final int maxThreads, final 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); } public void schedule(final SimpleThreadPoolTask task) { if (task == null) { throw new IllegalArgumentException("null task"); } final Object monitor = this.taskMonitor; synchronized (monitor) { if (this.numIdleThreads == 0) { this.addThreadImpl(); } this.taskList.add(task); monitor.notify(); } } public void cancel(final SimpleThreadPoolTask task) { synchronized (this.taskMonitor) { this.taskList.remove(task); } task.cancel(); } private void addThreadImpl() { if (this.numThreads < this.maxThreads) { final 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(); final Iterator<SimpleThreadPoolTask> i = this.runningSet.iterator(); while (i.hasNext()) { i.next().cancel(); } } } private class ThreadRunnable implements Runnable { public void run() { final Object monitor = taskMonitor; final LinkedList<SimpleThreadPoolTask> tl = taskList; final Set<SimpleThreadPoolTask> rs = runningSet; final 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()) { final 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 = taskList.removeFirst(); rs.add(task); } final Thread currentThread = Thread.currentThread(); final String baseName = currentThread.getName(); try { try { currentThread.setName(baseName + ":" + task.toString()); } catch (final Exception thrown) { logger.log(Level.WARNING, "run(): Unable to set task name.", thrown); } try { task.run(); } catch (final Exception thrown) { logger.log(Level.SEVERE, "run(): Error in task: " + task + ".", thrown); } } finally { currentThread.setName(baseName); } } catch (final Exception thrown) { logger.log(Level.SEVERE, "run(): Error in thread pool: " + SimpleThreadPool.this.name + ".", thrown); } } } } }