Home » Java » threadpool – Continuously submitting Runnable tasks to ExecutorService until work is done, getting java.util.concurrent.RejectedExecutionException-Exceptionshub

threadpool – Continuously submitting Runnable tasks to ExecutorService until work is done, getting java.util.concurrent.RejectedExecutionException-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

My multi-threaded class is supposed to carry out three operations – operation1, operation2, and operation3 – on a number of objects of the class ClassA, where each type of operation is dependant on the earlier operation. For this, I have tried to implement the producer-consumer pattern using a number of BlockingQueues and an ExecutorService.

final ExecutorService executor = ForkJoinPool.commonPool();
final BlockingQueue<ClassA> operationOneQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> operationTwoQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> operationThreeQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> resultQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);

The operations are implemented like this:

void doOperationOne() throws InterruptedException {
    ClassA objectA = operationOneQueue.take();
    objectA.operationOne();
    operationTwoQueue.put(objectA);
}

where each type of operation has its own corresponding method, with its “own” in-queue and out-queue. Each operation method calls the appropriate method on the ClassA object. The method doOperationThree puts ClassA objects in the resultQueue, meaning they have been completely processed.

First, I fill the operationOneQueue with all ClassA objects that are to be operated on. Then, I try to assign executable tasks to the ExecutorService like this:

    while (resultQueue.size() < NO_OF_CLASS_A_OBJECTS) {
        executor.execute(() -> {
            try {
                doOperationOne();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        executor.execute(() -> {
            try {
                doOperationTwo();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        executor.execute(() -> {
            try {
                doOperationThree();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
    executor.shutdown();

Running my program, I get a java.util.concurrent.RejectedExecutionException.

    Operation1: ClassA object 0
    Operation2: ClassA object 0
    Operation1: ClassA object 1
    Operation3: ClassA object 0
    ....
    Operation1: ClassA object 46
    Operation2: ClassA object 45
    Operation3: ClassA object 45
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Queue capacity exceeded
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.growArray(ForkJoinPool.java:912)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.lockedPush(ForkJoinPool.java:867)
at java.base/java.util.concurrent.ForkJoinPool.externalPush(ForkJoinPool.java:1911)
at java.base/java.util.concurrent.ForkJoinPool.externalSubmit(ForkJoinPool.java:1930)
at java.base/java.util.concurrent.ForkJoinPool.execute(ForkJoinPool.java:2462)
at concurrent.operations.Program1.main(Program1.java:96)

What am I doing wrong? How can I achieve this without over-saturating the thread pool?

Edit: Full disclosure – this is homework with some requirements. 1. I must use ForkJoinPool.commonPool() and must not set the number of threads myself, 2. I must use the consumer-producer pattern, and 3. I must not modify ClassA.

How to&Answers:

I really like doing concurrent stuff, so I did try writing it. I did use CompletableFuture which a) does run in the ForkJoinPool.commonPool by default and b) makes the actual processing really easy:

while (true) {
    final ClassA nextOperation = queue.take();
    CompletableFuture.runAsync(nextOperation::operationOne)
        .thenRun(nextOperation::operationTwo)
        .thenRun(nextOperation::operationThree)
        .thenRun(() -> resultQueue.add(nextOperation));
}

This will take ClassA objects from the queue and execute all their operations concurrently, but in order.

You did leave out where the tasks are coming from, and whether you need the consumer to terminate. Generally you don’t want to, and it does make matters a bit more complicated.

private static final int COUNT = 10;
private static final Random RANDOM = new Random();

public static void main(String[] args) throws ExecutionException, InterruptedException {
    BlockingQueue<ClassA> runnables = new ArrayBlockingQueue<>(COUNT);
    BlockingQueue<ClassA> finished = new ArrayBlockingQueue<>(COUNT);

    // start producer
    ExecutorService createTaskExecutor = Executors.newSingleThreadExecutor();
    createTaskExecutor.submit(() -> fillQueue(runnables));
    // wait for all consumer tasks to finish
    while (finished.size() != COUNT) {
        try {
            // we need to poll instead of waiting forever
            // because the last tasks might still be running
            // while there are no others to add anymore
            // so we need to check again if all have finished in the meantime
            final ClassA nextOperation = runnables.poll(2, TimeUnit.SECONDS);
            if (nextOperation != null) {
                CompletableFuture.runAsync(nextOperation::operationOne)
                        .thenRun(nextOperation::operationTwo)
                        .thenRun(nextOperation::operationThree)
                        .thenRun(() -> finished.add(nextOperation));
            }
        } catch (InterruptedException e) {
            System.err.println("exception while retrieving next operation");
            // we will actually need to terminate now, or probably never will
            throw e;
        }
    }
    System.out.printf("finished tasks (%d):%n", finished.size());
    for (ClassA classA : finished) {
        System.out.printf("finished task %d%n", classA.designator);
    }
    createTaskExecutor.shutdown();
}

private static void fillQueue(BlockingQueue<ClassA> runnables) {
    // start thread filling the queue at random
    for (int i = 0; i < COUNT; i++) {
        runnables.add(new ClassA(i));
        try {
            Thread.sleep(RANDOM.nextInt(1_000));
        } catch (InterruptedException e) {
            System.err.println("failed to add runnable");
        }
    }
}

Since you didn’t provide ClassA, I used this one. It contains an identifier so you can track which is running at what time.

class ClassA {
    private static final Random RANDOM = new Random();
    public final int designator;

    public ClassA(int i) {
        designator = i;
    }

    public void operationOne() {
        System.out.printf("%d: operation 1%n", designator);
        sleep();
    }

    public void operationTwo() {
        System.out.printf("%d: operation 2%n", designator);
        sleep();
    }

    public void operationThree() {
        System.out.printf("%d: operation 3%n", designator);
        sleep();
    }

    private static void sleep() {
        try {
            Thread.sleep(RANDOM.nextInt(5_000));
        } catch (InterruptedException e) {
            System.err.println("interrupted while executing task");
        }
    }
}