/* * Copyright: Almende B.V. (2014), Rotterdam, The Netherlands * License: The Apache Software License, Version 2.0 */ package com.almende.util.threads; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; /** * The Class RunQueue. This is our own ThreadPool, with the following behavior: * -Unlimited queue * -Threadcount based on number of "Running" Threads, excluding "Blocked", * "Timed_waiting" and "Waiting" threads from the threadcount. * -Approximately nofCPU threads in Running state. */ public class RunQueue extends AbstractExecutorService { private static final Logger LOG = Logger.getLogger(RunQueue.class .getName()); private final Queue<Worker> workers = new ConcurrentLinkedQueue<Worker>(); private final Queue<Worker> free = new ConcurrentLinkedQueue<Worker>(); private final Queue<Worker> waiting = new ConcurrentLinkedQueue<Worker>(); private final Queue<Runnable> tasks = new ConcurrentLinkedQueue<Runnable>(); private final Scanner scanner = new Scanner( "RunQueue_Scanner"); private int nofCores; private int[] taskCnt = new int[1]; private static int maxtasks = -1; private static final int MAXTASKSPERWORKER = 1000; private boolean isShutdown = false; private final Object terminationLock = new Object(); private int interval = 100; private final int maxinterval = 500; private class Scanner extends Thread { public Scanner(final String name) { super(name); } @Override public void run() { for (;;) { try { Thread.sleep(interval); } catch (InterruptedException e) {} if (isShutdown) { return; } scan(); } } } private class Worker extends Thread { final private ReentrantLock lock = new ReentrantLock(); final private Condition condition = lock.newCondition(); private int taskCnt = 0; private Runnable task = null; private boolean isShutdown = false; private boolean isWaiting = false; public Worker() { this.setName("RunQueue_Worker"); this.start(); } public boolean runTask(final Runnable task) { if (this.task != null || isShutdown || isWaiting) { // early out return false; } if (task == null) { return true; } if (lock.tryLock()) { if (this.task != null) { lock.unlock(); return false; } this.task = task; condition.signalAll(); lock.unlock(); return true; } else { return false; } } @Override public void run() { while (!isShutdown) { try { lock.lockInterruptibly(); } catch (InterruptedException e1) { continue; } while (task == null) { if (isShutdown) { return; } try { condition.await(); } catch (InterruptedException e) {} } taskCnt++; task.run(); taskCnt--; task = null; if (!this.isWaiting) { task = getTask(); if (task == null) { free.add(this); } } else { isShutdown = true; } lock.unlock(); } if (task != null) { putTask(task); } threadTearDown(this); synchronized (terminationLock) { terminationLock.notify(); } } } /** * Instantiates a new run queue. */ public RunQueue() { nofCores = Runtime.getRuntime().availableProcessors(); if (nofCores < 4) { // Keep a minimum number of assumed cores, to prevent thread // starvation. nofCores = 4; } taskCnt[0] = 0; while (workers.size() < nofCores) { workers.add(new Worker()); } scanner.start(); } /** * Sets the max tasks. * * @param max * the new max tasks */ public void setMaxTasks(final int max) { maxtasks = max; if (maxtasks > 0) { synchronized (taskCnt) { taskCnt[0] = tasks.size(); } } else { synchronized (taskCnt) { taskCnt[0] = 0; } } } @Override public void shutdown() { isShutdown = true; scanner.interrupt(); } @Override public List<Runnable> shutdownNow() { isShutdown = true; scanner.interrupt(); for (Worker worker : workers) { worker.isShutdown = true; worker.interrupt(); } for (Worker worker : waiting) { worker.isShutdown = true; worker.interrupt(); } return new ArrayList<Runnable>(tasks); } @Override public boolean isShutdown() { return isShutdown; } @Override public boolean isTerminated() { return isShutdown && (workers.isEmpty() && waiting.isEmpty()); } @Override public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { final long sleepTime = TimeUnit.MILLISECONDS.convert(timeout, unit); synchronized (terminationLock) { while (!isTerminated()) { terminationLock.wait(sleepTime); } } return isTerminated(); } private boolean addTask(final Runnable command) { if (maxtasks > 0 && taskCnt[0] > maxtasks) { // TODO: find a way to limit this without a shared counter! For // example: runtime to add task > 1a2 ms? Thread thread = Thread.currentThread(); if (thread instanceof Worker) { if (((Worker) thread).taskCnt <= MAXTASKSPERWORKER) { // Do this task yourself! return false; } } } putTask(command); return true; } private void putTask(final Runnable command) { if (maxtasks > 0) { synchronized (taskCnt) { taskCnt[0]++; } } tasks.add(command); } private Runnable getTask() { final Runnable task = tasks.poll(); if (maxtasks > 0 && task != null) { synchronized (taskCnt) { taskCnt[0]--; } } return task; } @Override public void execute(final Runnable command) { if (command == null) { throw new NullPointerException( "Command to execute may never be null."); } if (isShutdown()) { LOG.warning("Execute called after shutdown, dropping command"); return; } Worker thread = getFreeThread(); while (thread != null) { if (thread.runTask(command)) { break; } thread = getFreeThread(); } if (thread == null) { if (!addTask(command)) { thread = (Worker) Thread.currentThread(); thread.taskCnt++; command.run(); thread.taskCnt--; } } } private Worker getFreeThread() { Worker res = free.poll(); if (res != null) { if (res.taskCnt == 0) { return res; } } res = workers.poll(); if (res != null) { workers.add(res); if (res.taskCnt == 0) { return res; } } return null; } private void threadWaiting(final Worker thread) { thread.isWaiting = true; thread.setName("RunQueue_Worker_waiting"); waiting.add(thread); workers.remove(thread); final Worker worker = new Worker(); workers.add(worker); free.add(worker); } private void threadTearDown(final Worker thread) { if (!thread.isShutdown) { thread.isShutdown = true; thread.interrupt(); } waiting.remove(thread); workers.remove(thread); free.remove(thread); } private void scan() { final Worker[] runn_arr; runn_arr = workers.toArray(new Worker[0]); final ArrayList<Worker> avail_arr = new ArrayList<Worker>( runn_arr.length); int len = runn_arr.length; int count = 0; for (final Worker thread : runn_arr) { switch (thread.getState()) { case TIMED_WAITING: // explicit no break case WAITING: // explicit no break case BLOCKED: if (thread.taskCnt > 0) { count++; threadWaiting(thread); } else { // Potential available thread. avail_arr.add(thread); } break; case TERMINATED: threadTearDown(thread); len--; break; default: break; } } if (!isShutdown && len < nofCores - 1) { for (int i = len; i < nofCores; i++) { final Worker thread = new Worker(); workers.add(thread); free.add(thread); avail_arr.add(thread); } } if (!isShutdown) { if (!tasks.isEmpty() && !avail_arr.isEmpty()) { Runnable task = null; for (Worker thread : avail_arr) { if (task == null) { task = getTask(); } if (task != null) { if (thread.runTask(task)) { count++; task = null; } } else { break; } } if (task != null) { putTask(task); } } } if (count == 0) { interval = Math.min(interval * 2, maxinterval); } else { while (count-- > 0) { interval = interval > 1 ? interval / 2 : 1; } } } public String toString() { return this.getClass().getName() + ": ru:" + workers.size() + " wa:" + waiting.size() + " t:" + tasks.size() + " nofCores:" + nofCores + " int:" + interval + " ms."; } }