All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.threads.QueuelessExecutor Maven / Gradle / Ivy

There is a newer version: 3.8.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2009, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.threads;

import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.jboss.logging.Logger;
import org.jboss.threads.management.BoundedThreadPoolExecutorMBean;

/**
 * A queueless thread pool.  If one or more threads are waiting for work when a task is submitted, it will be used.
 * Otherwise, if fewer than the maximum threads are started, a new thread is created.
 */
public final class QueuelessExecutor extends AbstractExecutorService implements BlockingExecutorService, BoundedThreadPoolExecutorMBean, ShutdownListenable {

    private static final Logger log = Logger.getLogger("org.jboss.threads.executor");

    private final SimpleShutdownListenable shutdownListenable = new SimpleShutdownListenable();
    private final ThreadFactory threadFactory;
    private final DirectExecutor taskExecutor;

    private final Lock lock = new ReentrantLock(false);
    private final Condition runnableDequeued = lock.newCondition();
    private final Condition nextReady = lock.newCondition();
    private final Condition workerDequeued = lock.newCondition();
    private final Condition taskEnqueued = lock.newCondition();
    private final Condition threadDeath = lock.newCondition();

    /**
     * Protected by {@link #lock}
     */
    private final Set runningThreads = new HashSet(256);

    /**
     * Protected by {@link #lock}, signal {@link #runnableDequeued} on clear
     */
    private Runnable workRunnable;

    /**
     * Protected by {@link #lock}, signal {@link #workerDequeued} on clear
     */
    private Worker waitingWorker;

    /**
     * Configuration value.
     * Protected by {@link #lock}
     */
    private long keepAliveTime;

    /**
     * Configuration value.
     * Protected by {@link #lock}
     */
    private int maxThreads;

    /**
     * Specify whether this executor blocks when no threads are available.
     * Protected by {@link #lock}
     */
    private boolean blocking;

    private Executor handoffExecutor;

    private boolean stop;

    //-- statistics --
    private int largestPoolSize;
    private int rejectedCount;

    public QueuelessExecutor(final ThreadFactory threadFactory, final DirectExecutor taskExecutor, final Executor handoffExecutor, final long keepAliveTime) {
        this.threadFactory = threadFactory;
        this.taskExecutor = taskExecutor;
        this.handoffExecutor = handoffExecutor;
        this.keepAliveTime = keepAliveTime;
    }

    public int getMaxThreads() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return maxThreads;
        } finally {
            lock.unlock();
        }
    }

    public void setMaxThreads(final int newSize) {
        if (newSize < 1) {
            throw new IllegalArgumentException("Pool size must be at least 1");
        }
        final Lock lock = this.lock;
        lock.lock();
        try {
            maxThreads = newSize;
        } finally {
            lock.unlock();
        }
    }

    public long getKeepAliveTime() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return keepAliveTime;
        } finally {
            lock.unlock();
        }
    }

    public void setKeepAliveTime(final long milliseconds) {
        final Lock lock = this.lock;
        lock.lock();
        try {
            keepAliveTime = milliseconds;
        } finally {
            lock.unlock();
        }
    }

    public int getCurrentThreadCount() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return runningThreads.size();
        } finally {
            lock.unlock();
        }
    }

    public int getLargestThreadCount() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return largestPoolSize;
        } finally {
            lock.unlock();
        }
    }

    public int getRejectedCount() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return rejectedCount;
        } finally {
            lock.unlock();
        }
    }

    /** {@inheritDoc} */
    public int getQueueSize() {
        return 0;
    }

    public boolean isBlocking() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return blocking;
        } finally {
            lock.unlock();
        }
    }

    public void setBlocking(final boolean blocking) {
        final Lock lock = this.lock;
        lock.lock();
        try {
            this.blocking = blocking;
        } finally {
            lock.unlock();
        }
    }

    public Executor getHandoffExecutor() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return handoffExecutor;
        } finally {
            lock.unlock();
        }
    }

    public void setHandoffExecutor(final Executor handoffExecutor) {
        final Lock lock = this.lock;
        lock.lock();
        try {
            this.handoffExecutor = handoffExecutor;
        } finally {
            lock.unlock();
        }
    }

    public void shutdown() {
        boolean callShutdownListener = false;
        final Lock lock = this.lock;
        lock.lock();
        try {
            if (! stop) {
                if (runningThreads.isEmpty()) {
                    callShutdownListener = true;
                } else {
                    for (Thread runningThread : runningThreads) {
                        runningThread.interrupt();
                    }
                }
            }
            stop = true;
            // wake up all waiters
            runnableDequeued.signalAll();
            nextReady.signalAll();
            workerDequeued.signalAll();
            taskEnqueued.signalAll();
        } finally {
            lock.unlock();
            if (callShutdownListener)
                shutdownListenable.shutdown();
        }
    }

    public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
        final Lock lock = this.lock;
        lock.lock();
        try {
            if (! stop) {
                throw new IllegalStateException("Not shut down");
            }
            final Date deadline = new Date(clipHigh(unit.toMillis(timeout) + System.currentTimeMillis()));
            final Condition threadDeath = this.threadDeath;
            while (! runningThreads.isEmpty() && threadDeath.awaitUntil(deadline));
            return runningThreads.isEmpty();
        } finally {
            lock.unlock();
        }
    }

    public List shutdownNow() {
        shutdown();
        // tasks are never queued
        return Collections.emptyList();
    }

    public boolean isShutdown() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return stop;
        } finally {
            lock.unlock();
        }
    }

    public boolean isTerminated() {
        final Lock lock = this.lock;
        lock.lock();
        try {
            return stop && runningThreads.isEmpty();
        } finally {
            lock.unlock();
        }
    }

    public void execute(final Runnable task) {
        if (task == null) {
            throw new NullPointerException("task is null");
        }
        final Executor executor;
        final Set runningThreads = this.runningThreads;
        final Condition runnableDequeued = this.runnableDequeued;
        final Lock lock = this.lock;
        Runnable workRunnable;
        lock.lock();
        try {
            for (;;) {
                if (stop) {
                    throw new StoppedExecutorException("Executor has been shut down");
                }
                final Worker waitingWorker;
                if ((waitingWorker = this.waitingWorker) != null) {
                    // a worker thread was waiting for a task; give it the task and wake it up
                    waitingWorker.setRunnable(task);
                    taskEnqueued.signal();
                    this.waitingWorker = null;
                    workerDequeued.signal();
                    return;
                }
                // no worker thread was waiting
                final int currentSize = runningThreads.size();
                if (currentSize < maxThreads) {
                    // if we haven't reached the thread limit yet, start up another thread
                    final Thread thread = threadFactory.newThread(new Worker(task));
                    if (thread == null) {
                        throw new ThreadCreationException();
                    }
                    if (! runningThreads.add(thread)) {
                        throw new ThreadCreationException("Unable to add new thread to the running set");
                    }
                    if (currentSize >= largestPoolSize) {
                        largestPoolSize = currentSize + 1;
                    }
                    thread.start();
                    return;
                }
                if (! blocking) {
                    // not blocking, not accepted
                    executor = handoffExecutor;
                    rejectedCount++;
                    // fall out to execute outside of lock
                    break;
                }
                workRunnable = this.workRunnable;
                if (workRunnable != null) {
                    // someone else is waiting for a worker, so we wait for them
                    try {
                        nextReady.await();
                        continue;
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new ExecutionInterruptedException();
                    }
                }
                this.workRunnable = task;
                try {
                    runnableDequeued.await();
                    if (this.workRunnable == null) {
                        // task was accepted
                        nextReady.signal();
                        return;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new ExecutionInterruptedException();
                } finally {
                    this.workRunnable = null;
                }
            }
        } finally {
            lock.unlock();
        }
        if (executor != null) {
            executor.execute(task);
        } else {
            throw new RejectedExecutionException();
        }
    }

    public void executeBlocking(final Runnable task) throws RejectedExecutionException, InterruptedException {
        if (task == null) {
            throw new NullPointerException("task is null");
        }
        final Set runningThreads = this.runningThreads;
        final Condition runnableDequeued = this.runnableDequeued;
        final Lock lock = this.lock;
        Runnable workRunnable;
        lock.lock();
        try {
            for (;;) {
                if (stop) {
                    throw new StoppedExecutorException("Executor has been shut down");
                }
                final Worker waitingWorker;
                if ((waitingWorker = this.waitingWorker) != null) {
                    // a worker thread was waiting for a task; give it the task and wake it up
                    waitingWorker.setRunnable(task);
                    taskEnqueued.signal();
                    this.waitingWorker = null;
                    workerDequeued.signal();
                    return;
                }
                // no worker thread was waiting
                final int currentSize = runningThreads.size();
                if (currentSize < maxThreads) {
                    // if we haven't reached the thread limit yet, start up another thread
                    final Thread thread = threadFactory.newThread(new Worker(task));
                    if (thread == null) {
                        throw new ThreadCreationException();
                    }
                    if (! runningThreads.add(thread)) {
                        throw new ThreadCreationException("Unable to add new thread to the running set");
                    }
                    if (currentSize >= largestPoolSize) {
                        largestPoolSize = currentSize + 1;
                    }
                    thread.start();
                    return;
                }
                workRunnable = this.workRunnable;
                if (workRunnable != null) {
                    // someone else is waiting for a worker, so we wait for them
                    nextReady.await();
                    continue;
                }
                this.workRunnable = task;
                try {
                    runnableDequeued.await();
                    if (this.workRunnable == null) {
                        // task was accepted
                        nextReady.signal();
                        return;
                    }
                } finally {
                    this.workRunnable = null;
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public void executeBlocking(final Runnable task, final long timeout, final TimeUnit unit) throws RejectedExecutionException, InterruptedException {
        if (task == null) {
            throw new NullPointerException("task is null");
        }
        long now = System.currentTimeMillis();
        final long deadline = now + unit.toMillis(timeout);
        if (deadline < 0L) {
            executeBlocking(task);
            return;
        }
        final Set runningThreads = this.runningThreads;
        final Condition runnableDequeued = this.runnableDequeued;
        final Lock lock = this.lock;
        Runnable workRunnable;
        lock.lock();
        try {
            for (;;) {
                if (stop) {
                    throw new StoppedExecutorException("Executor has been shut down");
                }
                final Worker waitingWorker;
                if ((waitingWorker = this.waitingWorker) != null) {
                    // a worker thread was waiting for a task; give it the task and wake it up
                    waitingWorker.setRunnable(task);
                    taskEnqueued.signal();
                    this.waitingWorker = null;
                    workerDequeued.signal();
                    return;
                }
                // no worker thread was waiting
                final int currentSize = runningThreads.size();
                if (currentSize < maxThreads) {
                    // if we haven't reached the thread limit yet, start up another thread
                    final Thread thread = threadFactory.newThread(new Worker(task));
                    if (thread == null) {
                        throw new ThreadCreationException();
                    }
                    if (! runningThreads.add(thread)) {
                        throw new ThreadCreationException("Unable to add new thread to the running set");
                    }
                    if (currentSize >= largestPoolSize) {
                        largestPoolSize = currentSize + 1;
                    }
                    thread.start();
                    return;
                }
                workRunnable = this.workRunnable;
                if (workRunnable != null) {
                    // someone else is waiting for a worker, so we wait for them
                    nextReady.await();
                    continue;
                }
                this.workRunnable = task;
                try {
                    final long remaining = deadline - now;
                    if (remaining <= 0L) {
                        throw new ExecutionTimedOutException();
                    }
                    runnableDequeued.await(remaining, TimeUnit.MILLISECONDS);
                    now = System.currentTimeMillis();
                    if (this.workRunnable == null) {
                        // task was accepted
                        nextReady.signal();
                        return;
                    }
                } finally {
                    this.workRunnable = null;
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public void executeNonBlocking(final Runnable task) throws RejectedExecutionException {
        if (task == null) {
            throw new NullPointerException("task is null");
        }
        final Executor executor;
        final Set runningThreads = this.runningThreads;
        final Lock lock = this.lock;
        lock.lock();
        try {
            for (;;) {
                if (stop) {
                    throw new StoppedExecutorException("Executor has been shut down");
                }
                final Worker waitingWorker;
                if ((waitingWorker = this.waitingWorker) != null) {
                    // a worker thread was waiting for a task; give it the task and wake it up
                    waitingWorker.setRunnable(task);
                    taskEnqueued.signal();
                    this.waitingWorker = null;
                    workerDequeued.signal();
                    return;
                }
                // no worker thread was waiting
                final int currentSize = runningThreads.size();
                if (currentSize < maxThreads) {
                    // if we haven't reached the thread limit yet, start up another thread
                    final Thread thread = threadFactory.newThread(new Worker(task));
                    if (thread == null) {
                        throw new ThreadCreationException();
                    }
                    if (! runningThreads.add(thread)) {
                        throw new ThreadCreationException("Unable to add new thread to the running set");
                    }
                    if (currentSize >= largestPoolSize) {
                        largestPoolSize = currentSize + 1;
                    }
                    thread.start();
                    return;
                }
                // not blocking, not accepted
                executor = handoffExecutor;
                rejectedCount++;
                // fall out to execute outside of lock
                break;
            }
        } finally {
            lock.unlock();
        }
        if (executor != null) {
            executor.execute(task);
        } else {
            throw new RejectedExecutionException();
        }
    }

    /** {@inheritDoc} */
    public  void addShutdownListener(final EventListener shutdownListener, final A attachment) {
        shutdownListenable.addShutdownListener(shutdownListener, attachment);
    }

    private static long clipHigh(long value) {
        return value < 0 ? Long.MAX_VALUE : value;
    }

    private final class Worker implements Runnable {

        private Runnable runnable;

        private Worker(final Runnable runnable) {
            this.runnable = runnable;
        }

        private void setRunnable(final Runnable runnable) {
            this.runnable = runnable;
        }

        private boolean awaitTimed(Condition condition, long idleSince) {
            final long end = clipHigh(System.currentTimeMillis() + keepAliveTime);
            long remaining = end - idleSince;
            if (remaining < 0L) {
                return false;
            }
            if (stop) return false;
            try {
                condition.await(remaining, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
            }
            return ! stop;
        }

        public void run() {
            final Lock lock = QueuelessExecutor.this.lock;
            final Condition workerDequeued = QueuelessExecutor.this.workerDequeued;
            final Condition runnableDequeued = QueuelessExecutor.this.runnableDequeued;
            final Condition taskEnqueued = QueuelessExecutor.this.taskEnqueued;
            final Set runningThreads = QueuelessExecutor.this.runningThreads;
            final DirectExecutor taskExecutor = QueuelessExecutor.this.taskExecutor;
            final Thread thread = Thread.currentThread();
            long idleSince = Long.MAX_VALUE;
            Runnable runnable = this.runnable;
            boolean last = false;
            this.runnable = null;
            try {
                MAIN: for (;;) {

                    // Run task
                    try {
                        taskExecutor.execute(runnable);
                    } catch (Throwable t) {
                        log.errorf(t, "Task execution failed for task %s", runnable);
                    }
                    idleSince = System.currentTimeMillis();
                    // Get next task
                    lock.lock();
                    try {
                        if (stop || runningThreads.size() > maxThreads) {
                            if (runningThreads.remove(thread) && runningThreads.isEmpty()) {
                                threadDeath.signalAll();
                                last = true;
                            }
                            return;
                        }
                        if ((runnable = workRunnable) != null) {
                            // there's a task, take it and continue on to the top of the loop
                            workRunnable = null;
                            runnableDequeued.signal();
                        } else {
                            // no executors are waiting, so we wait instead for an executor
                            while (waitingWorker != null) {
                                // wait... to wait
                                if (! awaitTimed(workerDequeued, idleSince)) return;
                                if ((runnable = workRunnable) != null) {
                                    // sniped an easy one by luck!
                                    continue MAIN;
                                }
                            }
                            waitingWorker = this;
                            try {
                                do {
                                    // wait for a job
                                    if (! awaitTimed(taskEnqueued, idleSince)) return;
                                } while ((runnable = this.runnable) == null);
                                this.runnable = null;
                            } finally {
                                if (waitingWorker == this) {
                                    waitingWorker = null;
                                    workerDequeued.signal();
                                }
                            }
                        }
                    } finally {
                        lock.unlock();
                    }
                }
            } finally {
                lock.lock();
                try {
                    if (stop && runningThreads.remove(thread) && runningThreads.isEmpty()) {
                        threadDeath.signalAll();
                        last = true;
                    }
                } finally {
                    lock.unlock();
                }
                if (last) {
                    shutdownListenable.shutdown();
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy