public MultiThreadedTaskRunner(final int numThreads) { _numThreads = numThreads; // if all threads are busy, newly submitted tasks will be run by caller final ThreadPoolExecutor.CallerRunsPolicy rejectionHandler = new ThreadPoolExecutor.CallerRunsPolicy(); // there will be a minimum task capacity of 1000, and preferably // numThreads * 10 (to avoid blocking buffer behaviour and to prepare // tasks for working threads in advance) final int taskCapacity = Math.max(1000, numThreads * 10); _threadFactory = new DaemonThreadFactory(); // This queue is a buffer for tasks to be processed. // It uses a hack that forces the ThreadPoolExecutor to block if a // caller tries to submit a // task when the pool is fully loaded. This will prevent to not process // input row // inside a RunRowProcessingPublisherTask thread (CallerRunsPolicy). If // processing of such row // would take a long time, it would cause other processing threads // starvation after they // finish their current work. So we rather block until the queue has // place in it. _workQueue = new AlwaysBlockingQueue<>(taskCapacity); _executorService = new ThreadPoolExecutor(numThreads, numThreads, 60, TimeUnit.SECONDS, _workQueue, _threadFactory, rejectionHandler); _executorScheduledService = new ScheduledThreadPoolExecutor(1); _executorScheduledService.setMaximumPoolSize(50); }