ThreadPoolExecutor Block When Queue Is Full?

前端 未结 8 1802
无人及你
无人及你 2020-11-29 17:42

I am trying to execute lots of tasks using a ThreadPoolExecutor. Below is a hypothetical example:

def workQueue = new ArrayBlockingQueue(3, f         


        
相关标签:
8条回答
  • 2020-11-29 18:24

    Ok, old thread but this is what I found when searching for blocking thread executor. My code tries to get a semaphore when the task is submitted to the task queue. This blocks if there are no semaphores left. As soon as a task is done the semaphore is released with the decorator. Scary part is that there is a possibility of losing semaphore but that could be solved with for example a timed job that just clears semaphores on a timed basis.

    So here my solution:

    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()
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 18:27

    In some very narrow circumstances, you can implement a java.util.concurrent.RejectedExecutionHandler that does what you need.

    RejectedExecutionHandler block = new RejectedExecutionHandler() {
      rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
         executor.getQueue().put( r );
      }
    };
    
    ThreadPoolExecutor pool = new ...
    pool.setRejectedExecutionHandler(block);
    

    Now. This is a very bad idea for the following reasons

    • It's prone to deadlock because all the threads in the pool may die before the thing you put in the queue is visible. Mitigate this by setting a reasonable keep alive time.
    • The task is not wrapped the way your Executor may expect. Lots of executor implementations wrap their tasks in some sort of tracking object before execution. Look at the source of yours.
    • Adding via getQueue() is strongly discouraged by the API, and may be prohibited at some point.

    A almost-always-better strategy is to install ThreadPoolExecutor.CallerRunsPolicy which will throttle your app by running the task on the thread which is calling execute().

    However, sometimes a blocking strategy, with all its inherent risks, is really what you want. I'd say under these conditions

    • You only have one thread calling execute()
    • You have to (or want to) have a very small queue length
    • You absolutely need to limit the number of threads running this work (usually for external reasons), and a caller-runs strategy would break that.
    • Your tasks are of unpredictable size, so caller-runs could introduce starvation if the pool was momentarily busy with 4 short tasks and your one thread calling execute got stuck with a big one.

    So, as I say. It's rarely needed and can be dangerous, but there you go.

    Good Luck.

    0 讨论(0)
提交回复
热议问题