我正在尝试使用ThreadPoolExecutor执行大量任务。以下是一个假设的例子:
def workQueue = new ArrayBlockingQueue<Runnable>(3, false)
def threadPoolExecutor = new ThreadPoolExecutor(3, 3, 1L, TimeUnit.HOURS, workQueue)
for(int i = 0; i < 100000; i++)
threadPoolExecutor.execute(runnable)
问题是我很快得到java.util.concurrent.RejectedExecutionException
,因为任务数量超过了工作队列的大小。但是,我正在寻找的所需行为是让主线程阻塞,直到队列中有空间。实现这一目标的最佳方法是什么?
答案 0 :(得分:56)
在一些非常狭窄的情况下,您可以实现执行所需操作的java.util.concurrent.RejectedExecutionHandler。
RejectedExecutionHandler block = new RejectedExecutionHandler() {
rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
executor.getQueue().put( r );
}
};
ThreadPoolExecutor pool = new ...
pool.setRejectedExecutionHandler(block);
现在。由于以下原因,一个非常糟糕的主意
一个几乎总是更好的策略是安装ThreadPoolExecutor.CallerRunsPolicy,它将通过在调用execute()的线程上运行任务来限制你的app。
但是,有时候一种具有所有固有风险的阻止策略实际上就是你想要的。我会说在这些条件下
所以,正如我所说。它很少需要并且可能很危险,但是你去了。
祝你好运。
答案 1 :(得分:5)
您可以使用ExecutorService service = new ThreadPoolExecutor(
3,
3,
1,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(6, false)
);
Semaphore lock = new Semaphore(6); // equal to queue capacity
for (int i = 0; i < 100000; i++ ) {
try {
lock.acquire();
service.submit(() -> {
try {
task.run();
} finally {
lock.release();
}
});
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
阻止线程进入池中。
grafana
有些问题:
队列大小应高于核心线程数。如果我们要使队列大小为3,最终会发生什么:
上面的示例转换为线程主线程阻止线程1.它可能看起来像一个小周期,但现在将频率乘以天和月。突然之间,短时间内浪费了大量时间。
答案 2 :(得分:5)
您需要做的是将ThreadPoolExecutor包装到Executor中,它明确限制其中并发执行的操作量:
private static class BlockingExecutor implements Executor {
final Semaphore semaphore;
final Executor delegate;
private BlockingExecutor(final int concurrentTasksLimit, final Executor delegate) {
semaphore = new Semaphore(concurrentTasksLimit);
this.delegate = delegate;
}
@Override
public void execute(final Runnable command) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
final Runnable wrapped = () -> {
try {
command.run();
} finally {
semaphore.release();
}
};
delegate.execute(wrapped);
}
}
您可以将concurrentTasksLimit调整为委托执行程序的threadPoolSize + queueSize,它几乎可以解决您的问题
答案 3 :(得分:1)
在这种情况下,这是我的代码段:
public void executeBlocking( Runnable command ) {
if ( threadPool == null ) {
logger.error( "Thread pool '{}' not initialized.", threadPoolName );
return;
}
ThreadPool threadPoolMonitor = this;
boolean accepted = false;
do {
try {
threadPool.execute( new Runnable() {
@Override
public void run() {
try {
command.run();
}
// to make sure that the monitor is freed on exit
finally {
// Notify all the threads waiting for the resource, if any.
synchronized ( threadPoolMonitor ) {
threadPoolMonitor.notifyAll();
}
}
}
} );
accepted = true;
}
catch ( RejectedExecutionException e ) {
// Thread pool is full
try {
// Block until one of the threads finishes its job and exits.
synchronized ( threadPoolMonitor ) {
threadPoolMonitor.wait();
}
}
catch ( InterruptedException ignored ) {
// return immediately
break;
}
}
} while ( !accepted );
}
threadPool是java.util.concurrent.ExecutorService的本地实例,已经初始化了。
答案 4 :(得分:1)
这就是我最终做的事情:
int NUM_THREADS = 6;
Semaphore lock = new Semaphore(NUM_THREADS);
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 100000; i++) {
try {
lock.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
pool.execute(() -> {
try {
// Task logic
} finally {
lock.release();
}
});
}
答案 5 :(得分:0)
我使用自定义RejectedExecutionHandler解决了这个问题,它只是暂时阻塞调用线程,然后再次尝试提交任务:
SELECT
S.CodeStudent,
S.Name,
S.LastName,
S.BirthDate,
(SELECT TOP 1 ST.TitleStudy FROM Studies ST WHERE ST.CodeStudent = 10 AND ST.TypeStudy = 'Technical') AS TitleTechnical,
(SELECT TOP 1 ST.DateStudy FROM Studies ST WHERE ST.CodeStudent = 10 AND ST.TypeStudy = 'Technical') AS DateTechnical,
...
FROM Student S
WHERE S.CodeStudent = 10
此类只能在线程池执行程序中用作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);
}
}
我看到的唯一不足之处是调用线程可能会被锁定的时间略长于严格必要(最长250ms)。对于许多短期运行的任务,可能会将等待时间减少到10ms左右。此外,由于这个执行程序实际上是递归调用的,因此很长时间等待线程可用(小时)可能导致堆栈溢出。
然而,我个人喜欢这种方法。它结构紧凑,易于理解,效果很好。我错过了什么重要的事情吗?
答案 6 :(得分:0)
一个相当简单的选择是将您的BlockingQueue
包装为在调用put(..)
时调用offer(..)
的实现:
public class BlockOnOfferAdapter<T> implements BlockingQueue<T> {
(..)
public boolean offer(E o) {
try {
delegate.put(o);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
return true;
}
(.. implement all other methods simply by delegating ..)
}
之所以起作用,是因为默认情况下,put(..)
会等到队列满时see:
/**
* Inserts the specified element into this queue, waiting if necessary
* for space to become available.
*
* @param e the element to add
* @throws InterruptedException if interrupted while waiting
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
void put(E e) throws InterruptedException;
无需捕获RejectedExecutionException
或进行复杂的锁定。
答案 7 :(得分:0)
好,旧线程,但这是我在搜索阻塞线程执行程序时发现的。当任务提交到任务队列时,我的代码尝试获取信号量。如果没有信号量,此功能将阻止。任务完成后,信号量便随装饰器一起释放。令人恐惧的是,有可能丢失信号量,但是可以通过例如定时清除信号量的定时作业来解决。
这是我的解决方案:
class BlockingThreadPoolTaskExecutor(concurrency: Int) : ThreadPoolTaskExecutor() {
companion object {
lateinit var semaphore: Semaphore
}
init {
semaphore = Semaphore(concurrency)
val semaphoreTaskDecorator = SemaphoreTaskDecorator()
this.setTaskDecorator(semaphoreTaskDecorator)
}
override fun <T> submit(task: Callable<T>): Future<T> {
log.debug("submit")
semaphore.acquire()
return super.submit(task)
}
}
private class SemaphoreTaskDecorator : TaskDecorator {
override fun decorate(runnable: Runnable): Runnable {
log.debug("decorate")
return Runnable {
try {
runnable.run()
} finally {
log.debug("decorate done")
semaphore.release()
}
}
}
}