Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
当java.util.concurrent.ThreadPoolExecutor有等待任务时动态调整其大小_Java_Multithreading_Concurrency - Fatal编程技术网

当java.util.concurrent.ThreadPoolExecutor有等待任务时动态调整其大小

当java.util.concurrent.ThreadPoolExecutor有等待任务时动态调整其大小,java,multithreading,concurrency,Java,Multithreading,Concurrency,我正在使用java.util.concurrent.ThreadPoolExecutor并行处理许多项。虽然线程本身工作正常,但有时由于线程中发生的操作,我们会遇到其他资源限制,这使我们希望减少池中的线程数 我想知道是否有一种方法可以在线程实际工作时降低线程的数量。我知道您可以调用setMaximumPoolSize()和/或setCorePoolSize(),但这些函数仅在线程空闲时调整池的大小,但在队列中没有任务等待之前,它们不会变为空闲。据我所知,这是不可能的 您可以实现beforeExe

我正在使用
java.util.concurrent.ThreadPoolExecutor
并行处理许多项。虽然线程本身工作正常,但有时由于线程中发生的操作,我们会遇到其他资源限制,这使我们希望减少池中的线程数


我想知道是否有一种方法可以在线程实际工作时降低线程的数量。我知道您可以调用
setMaximumPoolSize()
和/或
setCorePoolSize()
,但这些函数仅在线程空闲时调整池的大小,但在队列中没有任务等待之前,它们不会变为空闲。

据我所知,这是不可能的

您可以实现beforeExecute方法来检查一些布尔值,并强制线程暂时停止。请记住,它们将包含一个在重新启用之前不会执行的任务

或者,您可以实现afterExecute,在饱和时抛出RuntimeException。这将有效地导致线程死亡,并且由于执行器将高于最大值,因此不会创建新线程


我也不建议你这样做。相反,尝试寻找其他方法来控制导致问题的任务的并发执行。可能是在一个单独的线程池中执行它们,工作线程的数量比较有限。

您完全可以。调用
setCorePoolSize(int)
将更改池的核心大小。对此方法的调用是线程安全的,并覆盖提供给
ThreadPoolExecutor
的构造函数的设置。如果要调整池大小,则剩余线程将在其当前作业队列完成后关闭(如果它们处于空闲状态,则将立即关闭)。如果要增加池大小,将尽快分配新线程。分配新线程的时间框架没有文档记录,但是在实现中,每次调用
execute
方法时都会执行新线程的分配

要将此属性与运行时可调作业场配对,可以将此属性(通过包装器或使用动态MBean导出器)公开为读写JMX属性,以创建一个非常好的动态可调批处理程序

要在运行时强制减少池大小(这是您的请求),必须将
ThreadPoolExecutor
子类化,并在
之前执行(Thread,Runnable)
方法中添加中断。中断线程不足以中断,因为这仅与等待状态交互,并且在处理
ThreadPoolExecutor
任务期间,线程不会进入可中断状态

我最近遇到了同样的问题,试图在执行所有提交的任务之前强制终止线程池。为了实现这一点,我仅在将线程的
UncaughtExceptionHandler
替换为期望我的特定异常并丢弃它之后,才通过抛出运行时异常来中断线程

/**
 * A runtime exception used to prematurely terminate threads in this pool.
 */
static class ShutdownException
extends RuntimeException {
    ShutdownException (String message) {
        super(message);
    }
}

/**
 * This uncaught exception handler is used only as threads are entered into
 * their shutdown state.
 */
static class ShutdownHandler 
implements UncaughtExceptionHandler {
    private UncaughtExceptionHandler handler;

    /**
     * Create a new shutdown handler.
     *
     * @param handler The original handler to deligate non-shutdown
     * exceptions to.
     */
    ShutdownHandler (UncaughtExceptionHandler handler) {
        this.handler = handler;
    }
    /**
     * Quietly ignore {@link ShutdownException}.
     * <p>
     * Do nothing if this is a ShutdownException, this is just to prevent
     * logging an uncaught exception which is expected.  Otherwise forward
     * it to the thread group handler (which may hand it off to the default
     * uncaught exception handler).
     * </p>
     */
    public void uncaughtException (Thread thread, Throwable throwable) {
        if (!(throwable instanceof ShutdownException)) {
            /* Use the original exception handler if one is available,
             * otherwise use the group exception handler.
             */
            if (handler != null) {
                handler.uncaughtException(thread, throwable);
            }
        }
    }
}
/**
 * Configure the given job as a spring bean.
 *
 * <p>Given a runnable task, configure it as a prototype spring bean,
 * injecting any necessary dependencices.</p>
 *
 * @param thread The thread the task will be executed in.
 * @param job The job to configure.
 *
 * @throws IllegalStateException if any error occurs.
 */
protected void beforeExecute (final Thread thread, final Runnable job) {
    /* If we're in shutdown, it's because spring is in singleton shutdown
     * mode.  This means we must not attempt to configure the bean, but
     * rather we must exit immediately (prematurely, even).
     */
    if (!this.isShutdown()) {
        if (factory == null) {
            throw new IllegalStateException(
                "This class must be instantiated by spring"
                );
        }

        factory.configureBean(job, job.getClass().getName());
    }
    else {
        /* If we are in shutdown mode, replace the job on the queue so the
         * next process will see it and it won't get dropped.  Further,
         * interrupt this thread so it will no longer process jobs.  This
         * deviates from the existing behavior of shutdown().
         */
        workQueue.add(job);

        thread.setUncaughtExceptionHandler(
            new ShutdownHandler(thread.getUncaughtExceptionHandler())
            );

        /* Throwing a runtime exception is the only way to prematurely
         * cause a worker thread from the TheadPoolExecutor to exit.
         */
        throw new ShutdownException("Terminating thread");
    }
}

这将中断f(n)=活动-请求的n个线程。如果有任何问题,ThreadPoolExecutor的分配策略相当持久。它使用保证执行的
finally
块保持提前终止。因此,即使终止了太多线程,它们也会重新填充。

解决方案是排空ThreadPoolExecutor队列,根据需要设置ThreadPoolExecutor大小,然后在其他线程结束后一个接一个地添加回线程。 ThreadPoolExecutor类中用于排出队列的方法是私有的,因此您必须自己创建它。代码如下:

/**
 * Drains the task queue into a new list. Used by shutdownNow.
 * Call only while holding main lock.
 */
public static List<Runnable> drainQueue() {
    List<Runnable> taskList = new ArrayList<Runnable>();
    BlockingQueue<Runnable> workQueue = executor.getQueue();
    workQueue.drainTo(taskList);
    /*
     * If the queue is a DelayQueue or any other kind of queue
     * for which poll or drainTo may fail to remove some elements,
     * we need to manually traverse and remove remaining tasks.
     * To guarantee atomicity wrt other threads using this queue,
     * we need to create a new iterator for each element removed.
     */
    while (!workQueue.isEmpty()) {
        Iterator<Runnable> it = workQueue.iterator();
        try {
            if (it.hasNext()) {
                Runnable r = it.next();
                if (workQueue.remove(r))
                    taskList.add(r);
            }
        } catch (ConcurrentModificationException ignore) {
        }
    }
    return taskList;
}
其中“executor”是您的线程池executor

现在您需要锁定/解锁方法:

public void lock() {
    try {
        Field mainLock = getMainLock();
        Method lock = mainLock.getType().getDeclaredMethod("lock", (Class[])null);
        lock.invoke(mainLock.get(executor), (Object[])null);
    } catch {
        ...
    } 
}

public void unlock() {
    try {
        Field mainLock = getMainLock();
        mainLock.setAccessible(true);
        Method lock = mainLock.getType().getDeclaredMethod("unlock", (Class[])null);
        lock.invoke(mainLock.get(executor), (Object[])null);
    } catch {
        ...
    }  
}
最后,您可以编写“setThreadsNumber”方法,它可以增加和减少ThreadPoolExecutor大小:

public void setThreadsNumber(int intValue) {
    boolean increasing = intValue > executor.getPoolSize();
    executor.setCorePoolSize(intValue);
    executor.setMaximumPoolSize(intValue);
    if(increasing){
        if(drainedQueue != null && (drainedQueue.size() > 0)){
            executor.submit(drainedQueue.remove(0));
        }
    } else {
        if(drainedQueue == null){
            lock();
            drainedQueue = drainQueue();
            unlock();
        }
    }
}

注意:显然,如果您执行N个并行线程,并且将这个数字更改为N-1,那么所有N个线程都将继续运行。当第一个线程结束时,不会执行新线程。从现在起,并行线程的数量将是您选择的。我也需要相同的解决方案,而且在JDK8中,setCorePoolSize()和setMaximumPoolSize()似乎确实产生了所需的结果。 我做了一个测试用例,在这个测试用例中,我向池中提交了4个任务,它们同时执行,我在它们运行时缩小了池的大小,并提交了另一个我想单独执行的runnable。然后我将池恢复到其原始大小。这是测试源

它产生以下输出(线程“50”是应该单独执行的线程)


是的,我知道一旦线程空闲,您可以进行更改并使其生效。然而,我发现当一个线程完成它的任务时,“空闲”不会发生,除非没有其他任务在等待。我在问是否有任何方法可以使更改立即生效,这样即使有等待的任务,它们也将由新的所需线程数处理(在我的示例中,我们希望立即减少所使用的并发资源量)。我更新了我的帖子,描述了我最近遇到的一个性质类似的问题的解决方案。请看一看。我不明白,为什么我对这个答案没有业力-(嘿,Scott回答得很好!!谢谢这是一个非常棒的回答。如果你读了OP的问题,投票率高的第二个答案最初并没有回答它。OP说了很多。后续编辑实现了这个答案中的建议。感谢这个例子!值得一提的是,使用Java 9+运行这个例子不会有什么效果hr还原core时显示一个
非法argumentException
public void lock() {
    try {
        Field mainLock = getMainLock();
        Method lock = mainLock.getType().getDeclaredMethod("lock", (Class[])null);
        lock.invoke(mainLock.get(executor), (Object[])null);
    } catch {
        ...
    } 
}

public void unlock() {
    try {
        Field mainLock = getMainLock();
        mainLock.setAccessible(true);
        Method lock = mainLock.getType().getDeclaredMethod("unlock", (Class[])null);
        lock.invoke(mainLock.get(executor), (Object[])null);
    } catch {
        ...
    }  
}
public void setThreadsNumber(int intValue) {
    boolean increasing = intValue > executor.getPoolSize();
    executor.setCorePoolSize(intValue);
    executor.setMaximumPoolSize(intValue);
    if(increasing){
        if(drainedQueue != null && (drainedQueue.size() > 0)){
            executor.submit(drainedQueue.remove(0));
        }
    } else {
        if(drainedQueue == null){
            lock();
            drainedQueue = drainQueue();
            unlock();
        }
    }
}
run:
test thread 2 enter
test thread 1 enter
test thread 3 enter
test thread 4 enter
test thread 1 exit
test thread 2 exit
test thread 3 exit
test thread 4 exit
test thread 50 enter
test thread 50 exit
test thread 1 enter
test thread 2 enter
test thread 3 enter
test thread 4 enter
test thread 1 exit
test thread 2 exit
test thread 3 exit
test thread 4 exit