如果ThreadPoolExecutor的submit()方法已饱和,如何使之阻塞?


102

我想创建一个ThreadPoolExecutor这样的方法,当它达到最大大小并且队列已满时,该submit()方法在尝试添加新任务时会阻塞。我是否需要为此实现一个自定义RejectedExecutionHandler,或者是否存在使用标准Java库执行此操作的现有方法?




2
@bacar,我不同意。这个问答集看起来更有价值(除了更老之外)。
JasonMArcher 2014年

Answers:


47

我刚刚发现的可能解决方案之一:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command)
            throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }
}

还有其他解决方案吗?我更喜欢基于此的东西,RejectedExecutionHandler因为这似乎是处理此类情况的标准方法。


2
在finally子句中释放信号量与获取信号量之间,是否存在竞争条件?
volni

2
如上所述,此实现存在缺陷,因为在任务完成之前释放了信号量。最好使用方法java.util.concurrent.ThreadPoolExecutor#afterExecute(Runnable,Throwable)
FelixM

2
@FelixM:使用java.util.concurrent.ThreadPoolExecutor#afterExecute(Runnable,Throwable)不能解决问题,因为afterExecute在java.util.concurrent.ThreadPoolExecutor#runWorker(Worker w)中的task.run()之后立即调用,从队列中获取下一个元素(查看openjdk 1.7.0.6的源代码)。
2013年

1
这个答案来自Brian Goetz的《 Java Concurrency in Practice》一书
orangepips,2013年

11
这个答案并不完全正确,评论也是如此。这段代码确实来自实践中的Java Concurrency,并且如果考虑到它的上下文是正确的。该书从字面上清楚地指出:“采用这种方法,请使用无边界队列(...),并将信号量的边界设置为等于池大小加上要允许的排队任务数”。有了无限制的队列,任务将永远不会被拒绝,因此重新抛出异常是完全没有用的!我认为,这也是书中未throw e;包含该词的原因。JCIP是正确的!
2015年

30

您可以使用ThreadPoolExecutor和blockingQueue:

public class ImageManager {
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(blockQueueSize);
    RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
    private ExecutorService executorService =  new ThreadPoolExecutor(numOfThread, numOfThread, 
        0L, TimeUnit.MILLISECONDS, blockingQueue, rejectedExecutionHandler);

    private int downloadThumbnail(String fileListPath){
        executorService.submit(new yourRunnable());
    }
}

我只想说这是实施起来非常有效的疯狂而又简单的解决方案!
伊万(Ivan)

58
这将在提交线程上运行被拒绝的任务。在功能上不符合OP的要求。
感知

4
这确实在“正在调用的线程”中运行任务,而不是阻塞以将其放入队列,这可能会产生一些不利影响,例如,如果多个线程以这种方式调用它,则将运行“队列大小”以上的作业,并且如果该任务花费的时间比预期的要长,您的“产生”线程可能不会使执行程序忙。但是在这里很棒!
rogerdpack 2015年

4
不推荐使用:当TPE饱和时,它不会阻塞。这仅是替代方案,而不是解决方案。
2015年

1
推荐:这非常适合“ TPE设计”和“自然阻塞”客户端线程,方法是让它们进行溢出尝试。这应该涵盖大多数用例,但是当然不是全部,您应该了解幕后的情况。
迈克

12

您应该使用CallerRunsPolicy,在调用线程中执行被拒绝的任务。这样,在完成该任务之前,它无法将任何新任务提交给执行者,这时将有一些空闲池线程或该过程将重复。

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html

从文档:

被拒绝的任务

当执行器已关闭时,并且在执行器对最大线程和工作队列容量使用有限范围且已饱和时,将拒绝在方法execute(java.lang.Runnable)中提交的新任务。无论哪种情况,execute方法都会调用其RejectedExecutionHandler的RejectedExecutionHandler.rejectedExecution(java.lang.Runnable,java.util.concurrent.ThreadPoolExecutor)方法。提供了四个预定义的处理程序策略:

  1. 在默认的ThreadPoolExecutor.AbortPolicy中,处理程序在拒绝时会抛出运行时RejectedExecutionException。
  2. 在ThreadPoolExecutor.CallerRunsPolicy中,调用执行自己的线程运行任务。这提供了一种简单的反馈控制机制,将降低新任务的提交速度。
  3. 在ThreadPoolExecutor.DiscardPolicy中,简单地删除了无法执行的任务。
  4. 在ThreadPoolExecutor.DiscardOldestPolicy中,如果未关闭执行程序,则将丢弃工作队列开头的任务,然后重试执行(该操作可能再次失败,导致重复执行此操作)。

另外,在调用ThreadPoolExecutor构造函数时,请确保使用有限队列,例如ArrayBlockingQueue 。否则,任何事情都不会被拒绝。

编辑:根据您的评论,将ArrayBlockingQueue的大小设置为等于线程池的最大大小,并使用AbortPolicy。

编辑2:好的,我明白你的意思了。那怎么办:覆盖beforeExecute()检查getActiveCount()不超过的方法,如果不超过getMaximumPoolSize(),请休眠并重试?


3
我想让并发执行的任务数量受到严格限制(受Executor中线程的数量限制),这就是为什么我不允许调用者线程自己执行这些任务的原因。
Fixpoint 2010年

1
AbortPolicy会导致调用方线程收到RejectedExecutionException,而我只需要阻止它即可。
Fixpoint 2010年

2
我要的是阻止功能,而不是睡眠和轮询功能;)
Fixpoint 2010年

@danben:您不是说CallerRunsPolicy吗?
user359996

7
CallerRunPolicy的问题在于,如果您只有一个线程生产者,那么如果长时间运行的任务碰巧被拒绝,则经常会不使用线程(因为线程池中的其他任务将在长时间运行的任务仍处于运行状态时完成)正在运行)。
亚当·根特

6

Hibernate有一个BlockPolicy简单的方法,可以做您想做的事情:

参见:Executors.java

/**
 * A handler for rejected tasks that will have the caller block until
 * space is available.
 */
public static class BlockPolicy implements RejectedExecutionHandler {

    /**
     * Creates a <tt>BlockPolicy</tt>.
     */
    public BlockPolicy() { }

    /**
     * Puts the Runnable to the blocking queue, effectively blocking
     * the delegating thread until space is available.
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        try {
            e.getQueue().put( r );
        }
        catch (InterruptedException e1) {
            log.error( "Work discarded, thread was interrupted while waiting for space to schedule: {}", r );
        }
    }
}

4
再次考虑,这是一个非常糟糕的主意。我不建议您使用它。有充分的理由在这里看到:stackoverflow.com/questions/3446011/...
内特穆雷

另外,按照OP的要求,它不使用“标准Java库”。删除?
user359996

1
哇,真丑。基本上,该解决方案会干扰TPE的内部。javadoc ThreadPoolExecutor甚至可以直截了当地说:“方法getQueue()允许出于监视和调试的目的访问工作队列。强烈建议不要将此方法用于任何其他目的。” 这是被如此众所周知库提供,绝对是悲伤地看到。
2015年

1
com.amazonaws.services.simpleworkflow.flow.worker.BlockCallerPolicy与此类似。
阿德里安·贝克

6

BoundedExecutor上面引述的回答实践中的Java并发只有在您为执行程序使用无限制队列,或者信号量绑定不大于队列大小时才能正确运行。信号量是提交线程和池中线程之间的状态共享,即使队列大小<绑定<=(队列大小+池大小),也可以使执行程序饱和。

使用CallerRunsPolicy仅在您的任务不会永远运行时才有效,在这种情况下,您的提交线程将rejectedExecution永远存在,而如果您的任务需要很长时间才能运行,则是一个坏主意,因为提交线程无法提交任何新任务或如果它本身正在运行任务,则执行其他任何操作。

如果那是不可接受的,那么我建议在提交任务之前检查执行程序的有界队列的大小。如果队列已满,请等待一小段时间再尝试再次提交。吞吐量会受到影响,但是我建议这是一个比其他提议的解决方案更简单的解决方案,并且可以确保不会有任何任务被拒绝。


我不确定在提交之前检查队列长度如何确保在具有多个任务生产者的多线程环境中不会拒绝任何任务。听起来不是线程安全的。
蒂姆(Tim)

5

我知道这是一个hack,但是在我看来,这里提供的大多数hack都是干净的;-)

由于ThreadPoolExecutor使用阻塞队列“ offer”而不是“ put”,因此可以覆盖阻塞队列的“ offer”行为:

class BlockingQueueHack<T> extends ArrayBlockingQueue<T> {

    BlockingQueueHack(int size) {
        super(size);
    }

    public boolean offer(T task) {
        try {
            this.put(task);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new BlockingQueueHack(5));

我测试了它,它似乎起作用了。实施超时策略留给读者练习。


请参阅stackoverflow.com/a/4522411/2601671,以获取清理版本。我同意,这是最干净的方法。
特伦顿

3

以下类包装了ThreadPoolExecutor并使用Semaphore进行阻止,然后工作队列已满:

public final class BlockingExecutor { 

    private final Executor executor;
    private final Semaphore semaphore;

    public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
        this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }

    private void execImpl (final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            // will never be thrown with an unbounded buffer (LinkedBlockingQueue)
            semaphore.release();
            throw e;
        }
    }

    public void execute (Runnable command) throws InterruptedException {
        execImpl(command);
    }
}

该包装器类基于Brian Goetz在《 Java Concurrency in Practice》一书中给出的解决方案。本书中的解决方案仅采用两个构造函数参数:Executor用于信号量的an 和bound。Fixpoint给出的答案显示了这一点。这种方法有一个问题:它可能进入池线程繁忙,队列已满,但信号量刚刚释放了许可的状态。(semaphore.release()在finally块中)。在这种状态下,新任务可以获取刚刚释放的许可,但是由于任务队列已满而被拒绝。当然,这不是您想要的。您想在这种情况下屏蔽。

为了解决这个问题,正如JCiP明确提到的,我们必须使用无界队列。信号量起着保护作用,提供了虚拟队列大小的效果。这样做的副作用是,该单元可能包含maxPoolSize + virtualQueueSize + maxPoolSize任务。这是为什么?因为 semaphore.release()在finally块中。如果所有池线程同时调用此语句,则maxPoolSize则会释放许可,从而允许相同数量的任务进入该单元。如果我们使用的是有界队列,那么它仍然会满,导致任务被拒绝。现在,因为我们知道只有在线程池线程即将完成时才会发生这种情况,所以这不是问题。我们知道池线程不会阻塞,因此任务很快就会从队列中提取。

但是您可以使用有界队列。只要确保其大小相等即可virtualQueueSize + maxPoolSize。较大的尺寸是没有用的,信号量将阻止更多的物品进入。较小的尺寸将导致任务被拒绝。任务被拒绝的机会随着大小的减小而增加。例如,假设您要使用maxPoolSize = 2和virtualQueueSize = 5的有界执行程序。然后使用5 + 2 = 7许可和5 + 2 = 7的实际队列大小的信号量。这样,单位中可以存在的实际任务数就是2 + 5 + 2 = 9。当执行程序已满(队列中有5个任务,线程池中有2个任务,因此0个许可)并且ALL池线程释放了它们的许可时,进入的任务就可以正确地获得2个许可。

现在,JCiP的解决方案使用起来有些麻烦,因为它没有强制执行所有这些约束(无限制的队列,或受那些数学限制的约束,等等)。我认为,这仅是一个很好的示例,它说明了如何基于已经可用的部分构建新的线程安全类,而不能作为成熟的可重用类。我认为后者不是作者的意图。


2

您可以像这样使用自定义RejectedExecutionHandler

ThreadPoolExecutor tp= new ThreadPoolExecutor(core_size, // core size
                max_handlers, // max size 
                timeout_in_seconds, // idle timeout 
                TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // This will block if the queue is full
                        try {
                            executor.getQueue().put(r);
                        } catch (InterruptedException e) {
                            System.err.println(e.getMessage());
                        }

                    }
                });

1
getQueue()的文档明确提到访问任务队列主要用于调试和监视。
Chadi

0

创建自己的阻塞队列,供执行程序使用,并使用您要查找的阻塞行为,同时始终返回可用的剩余容量(确保执行程序不会尝试创建比其核心池更多的线程,也不会触发拒绝处理程序)。

我相信这将使您获得所需的阻止行为。拒绝处理程序永远不会满足要求,因为这表明执行者无法执行任务。我可以想象的是,您会在处理程序中得到某种形式的“忙等待”。那不是您想要的,您想要阻止执行者的执行者队列...


2
ThreadPoolExecutor使用offer方法将任务添加到队列。如果我创建了一个自定义BlockingQueue阻止的自定义offer,则会破坏BlockingQueue合同。
Fixpoint 2010年

@Shooshpanchick,这将破坏BlockingQueues合同。所以呢?如果您如此热衷,则可以仅
Submit

另请参见另一个问题的答案,该问题阐明了该替代方法。
罗伯特·图珀洛-施内克

是否有ThreadPoolExecutor实现为什么要使用offer而不是put(阻止版本)的原因?另外,如果有一种方法可以让客户端代码告诉何时使用哪个客户端代码,那么尝试手动定制解决方案的许多人就会松了一口气
asgs,

0

为避免@FixPoint解决方案出现问题。可以使用ListeningExecutorService并在FutureCallback中释放信号onSuccess和onFailure。


这与仅仅包装包有相同的内在问题,因为Runnable在正常清理工人之前仍然会调用这些方法ThreadPoolExecutor。那就是您仍然需要处理拒绝异常。
亚当·根特

0

最近,我发现这个问题也有同样的问题。OP并未明确表示,但我们不想使用在RejectedExecutionHandler提交者线程上执行任务的,因为如果此任务是长时间运行的任务,则会浪费工作线程。

阅读所有答案和评论,特别是使用信号灯的有缺陷的解决方案,或者使用afterExecuteI仔细查看ThreadPoolExecutor的代码,看看是否有解决方法。令我惊讶的是,有超过2000行(注释)代码,其中一些使我感到头晕。考虑到我实际上有一个非常简单的要求-一个生产者,几个消费者,让生产者在没有消费者可以承担工作时阻止它-我决定推出自己的解决方案。这不是一个ExecutorService而是一个Executor。而且它不会根据工作负载调整线程数,而是仅保留固定数量的线程,这也符合我的要求。这是代码。随意大声疾呼:-)

package x;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;

/**
 * distributes {@code Runnable}s to a fixed number of threads. To keep the
 * code lean, this is not an {@code ExecutorService}. In particular there is
 * only very simple support to shut this executor down.
 */
public class ParallelExecutor implements Executor {
  // other bounded queues work as well and are useful to buffer peak loads
  private final BlockingQueue<Runnable> workQueue =
      new SynchronousQueue<Runnable>();
  private final Thread[] threads;

  /*+**********************************************************************/
  /**
   * creates the requested number of threads and starts them to wait for
   * incoming work
   */
  public ParallelExecutor(int numThreads) {
    this.threads = new Thread[numThreads];
    for(int i=0; i<numThreads; i++) {
      // could reuse the same Runner all over, but keep it simple
      Thread t = new Thread(new Runner());
      this.threads[i] = t;
      t.start();
    }
  }
  /*+**********************************************************************/
  /**
   * returns immediately without waiting for the task to be finished, but may
   * block if all worker threads are busy.
   * 
   * @throws RejectedExecutionException if we got interrupted while waiting
   *         for a free worker
   */
  @Override
  public void execute(Runnable task)  {
    try {
      workQueue.put(task);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RejectedExecutionException("interrupt while waiting for a free "
          + "worker.", e);
    }
  }
  /*+**********************************************************************/
  /**
   * Interrupts all workers and joins them. Tasks susceptible to an interrupt
   * will preempt their work. Blocks until the last thread surrendered.
   */
  public void interruptAndJoinAll() throws InterruptedException {
    for(Thread t : threads) {
      t.interrupt();
    }
    for(Thread t : threads) {
      t.join();
    }
  }
  /*+**********************************************************************/
  private final class Runner implements Runnable {
    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        Runnable task;
        try {
          task = workQueue.take();
        } catch (InterruptedException e) {
          // canonical handling despite exiting right away
          Thread.currentThread().interrupt(); 
          return;
        }
        try {
          task.run();
        } catch (RuntimeException e) {
          // production code to use a logging framework
          e.printStackTrace();
        }
      }
    }
  }
}

0

我相信,通过使用java.util.concurrent.Semaphore和委派的行为,可以使用非常优雅的方法来解决此问题Executor.newFixedThreadPool。新的执行程序服务仅在有线程执行时才执行新任务。阻塞由信号量管理,允许数量等于线程数量。任务完成后,它将返回许可。

public class FixedThreadBlockingExecutorService extends AbstractExecutorService {

private final ExecutorService executor;
private final Semaphore blockExecution;

public FixedThreadBlockingExecutorService(int nTreads) {
    this.executor = Executors.newFixedThreadPool(nTreads);
    blockExecution = new Semaphore(nTreads);
}

@Override
public void shutdown() {
    executor.shutdown();
}

@Override
public List<Runnable> shutdownNow() {
    return executor.shutdownNow();
}

@Override
public boolean isShutdown() {
    return executor.isShutdown();
}

@Override
public boolean isTerminated() {
    return executor.isTerminated();
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    return executor.awaitTermination(timeout, unit);
}

@Override
public void execute(Runnable command) {
    blockExecution.acquireUninterruptibly();
    executor.execute(() -> {
        try {
            command.run();
        } finally {
            blockExecution.release();
        }
    });
}

我实现了Java Concurrency in Practice中描述的BoundedExecutor,并指出必须将公平性标志设置为true来初始化信号量,以确保在发出订单请求时提供了信号量许可。请参阅docs.oracle.com/javase/7/docs/api/java/util/concurrent/…。有关详细信息
Prahalad Deshpande 2015年

0

过去我有同样的需求:一种阻塞队列,每个队列由共享线程池支持,每个客户端的大小固定。我最终编写了自己的ThreadPoolExecutor:

UserThreadPoolExecutor(阻塞队列(每个客户端)+线程池(在所有客户端之间共享))

参见:https : //github.com/d4rxh4wx/UserThreadPoolExecutor

从共享的ThreadPoolExecutor中为每个UserThreadPoolExecutor提供最大线程数

每个UserThreadPoolExecutor可以:

  • 如果未达到任务的配额,则将任务提交给共享线程池执行程序。如果达到其配额,则将作业排队(等待队列的非消耗性阻塞)。一旦完成其提交的任务之一,配额就会减少,从而允许将另一个任务等待提交给ThreadPoolExecutor
  • 等待其余任务完成

0

我在弹性搜索客户端中找到了此拒绝策略。它在阻塞队列上阻塞了调用者线程。下面的代码

 static class ForceQueuePolicy implements XRejectedExecutionHandler 
 {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
        {
            try 
            {
                executor.getQueue().put(r);
            } 
            catch (InterruptedException e) 
            {
                //should never happen since we never wait
                throw new EsRejectedExecutionException(e);
            }
        }

        @Override
        public long rejected() 
        {
            return 0;
        }
}

0

最近,我需要实现类似的目标,但是需要在上ScheduledExecutorService

我还必须确保我处理了传递给方法的延迟,并确保要么按调用者的预期在任务提交时执行该任务,否则就失败了,从而抛出RejectedExecutionException

ScheduledThreadPoolExecutor内部执行或提交任务的其他方法,#schedule仍然会依次调用覆盖的方法。

import java.util.concurrent.*;

public class BlockingScheduler extends ScheduledThreadPoolExecutor {
    private final Semaphore maxQueueSize;

    public BlockingScheduler(int corePoolSize,
                             ThreadFactory threadFactory,
                             int maxQueueSize) {
        super(corePoolSize, threadFactory, new AbortPolicy());
        this.maxQueueSize = new Semaphore(maxQueueSize);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(delay));
        return super.schedule(command, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(callable, unit.toMillis(delay));
        return super.schedule(callable, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleAtFixedRate(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long period,
                                                     TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleWithFixedDelay(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable t) {
        super.afterExecute(runnable, t);
        try {
            if (t == null && runnable instanceof Future<?>) {
                try {
                    ((Future<?>) runnable).get();
                } catch (CancellationException | ExecutionException e) {
                    t = e;
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                System.err.println(t);
            }
        } finally {
            releaseQueueUsage();
        }
    }

    private long beforeSchedule(Runnable runnable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(runnable, this);
            return 0;
        }
    }

    private long beforeSchedule(Callable callable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(new FutureTask(callable), this);
            return 0;
        }
    }

    private long getQueuePermitAndModifiedDelay(long delay) throws InterruptedException {
        final long beforeAcquireTimeStamp = System.currentTimeMillis();
        maxQueueSize.tryAcquire(delay, TimeUnit.MILLISECONDS);
        final long afterAcquireTimeStamp = System.currentTimeMillis();
        return afterAcquireTimeStamp - beforeAcquireTimeStamp;
    }

    private void releaseQueueUsage() {
        maxQueueSize.release();
    }
}

我在这里有代码,将不胜感激任何反馈。 https://github.com/AmitabhAwasthi/BlockingScheduler


该答案完全取决于外部链接的内容。如果它们变得无效,您的答案将毫无用处。因此,请编辑您的答案,并至少添加一份总结。谢谢!
Fabio说恢复Monica

@fabio:感谢您指出。我在其中添加了代码,以便现在对读者来说更有意义。感谢您的评论:)
Dev Amitabh


0

我并不总是喜欢CallerRunsPolicy,尤其是因为它允许被拒绝的任务“跳过队列”并在之前提交的任务之前执行。此外,在调用线程上执行任务可能比等待第一个插槽可用要花费更长的时间。

我使用自定义的RejectedExecutionHandler解决了这个问题,该方法只是阻塞了调用线程一段时间,然后尝试再次提交任务:

public class BlockWhenQueueFull implements RejectedExecutionHandler {

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        // The pool is full. Wait, then try again.
        try {
            long waitMs = 250;
            Thread.sleep(waitMs);
        } catch (InterruptedException interruptedException) {}

        executor.execute(r);
    }
}

此类只能在线程池执行程序中用作RejectedExecutinHandler,就像其他任何示例一样,例如:

executorPool = new ThreadPoolExecutor(1, 1, 10,
                                      TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
                                      new BlockWhenQueueFull());

我看到的唯一缺点是,调用线程可能被锁定的时间略长于严格必要的时间(最长250ms)。而且,由于此执行程序是有效地递归调用的,因此非常长的等待线程可用时间(小时)可能导致堆栈溢出。

尽管如此,我个人还是喜欢这种方法。它结构紧凑,易于理解并且运行良好。


1
就像您自己说的那样:这可能会产生stackoverflow。我不想在生产代码中拥有某些东西。
哈拉尔德

每个人都应该做出自己的决定。对于我的工作量,这不是问题。任务以秒为单位运行,而不是炸毁堆栈所需的时间。而且,几乎任何递归算法都可以说相同。那是在生产中永远不使用任何递归算法的原因吗?
TinkerTank
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.