ArrayBlockingQueue NoSuchElementException - PullRequest
       23

ArrayBlockingQueue NoSuchElementException

0 голосов
/ 23 сентября 2018

Просто для изучения, я написал следующий код для пользовательского пула потоков, который ссылается и редактирует код, показанный здесь .

Как показано в коде, я использую ArrayBlockingQueue для очереди задач.

Код:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class ThreadPoolService {
    private final BlockingQueue<Runnable> taskQueue;
    private final int corePoolSize;

    private ThreadPoolService(int corePoolSize) {
        this.corePoolSize = corePoolSize;
        this.taskQueue = new ArrayBlockingQueue<>(corePoolSize);
        ThreadPool[] threadPool = new ThreadPool[corePoolSize];
        for (int i = 0; i < corePoolSize; i++) {
            threadPool[i] = new ThreadPool();
            threadPool[i].start();
        }
    }

    public static ThreadPoolService newFixedThreadPool(int size) {
        return new ThreadPoolService(size);
    }

    public void execute(Runnable task) {
        try {
            taskQueue.offer(task, 10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class ThreadPool extends Thread {
        Runnable task;

        @Override
        public void run() {
            while (true) {
                try {
                    while (!taskQueue.isEmpty()) {
                        task = taskQueue.remove();
                        task.run();
                    }
                } catch (RuntimeException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadPoolService pool = ThreadPoolService.newFixedThreadPool(10);
        Runnable task1 = () -> {
            System.out.println(" Wait for sometime: -> " + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable task2 = () -> System.out.println(" Do  Task 2 -> " + Thread.currentThread().getName());
        Runnable task3 = () -> System.out.println(" Do  Task 3 -> " + Thread.currentThread().getName());
        Runnable task4 = () -> System.out.println(" Do  Task 4 -> " + Thread.currentThread().getName());
        List<Runnable> taskList = new ArrayList<>();
        taskList.add(task1);
        taskList.add(task2);
        taskList.add(task3);
        taskList.add(task4);
        for (Runnable task : taskList) {
            pool.execute(task);
        }
    }
}

Этот код иногда работает нормально, а иногда выдает ошибку.

Вывод успеха:

Do  Task 2 -> Thread-2
Wait for sometime: -> Thread-8
Do  Task 3 -> Thread-6
Do  Task 4 -> Thread-7

Вывод ошибки:

Do  Task 4 -> Thread-3
Do  Task 3 -> Thread-6
Wait for sometime: -> Thread-4
Do  Task 2 -> Thread-7
java.util.NoSuchElementException
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)

Я вижу, что причиной ошибки является попытка удалить элемент, когда очередь пуста.Но это не должно происходить, потому что я делаю проверку пустой очереди в строке № 42 (while (!taskQueue.isEmpty())).Что не так с кодом, а также почему он иногда запускается без ошибок?

Ответы [ 3 ]

0 голосов
/ 23 сентября 2018

BlockingQueue является поточно-ориентированным только на уровне отдельной операции. Я вижу check-then-act операцию в коде, который является сложной операцией, не являющейся поточно-ориентированной.Чтобы сделать этот код поточно-ориентированным, выполните check-then-act внутри синхронизированного блока и заблокируйте саму очередь.

synchronized(taskQueue) {
       while (!taskQueue.isEmpty()) {
             task = taskQueue.remove();
             task.run();
 }};

оптимизация: Если задача занимает много времени, выможет выполнить его за пределами синхронизированного блока.Чтобы другим потокам не приходилось ждать завершения текущей задачи.

0 голосов
/ 23 сентября 2018

Что не так с кодом?

Вы получаете доступ к полю taskQueue из нескольких потоков без синхронизации.Вы должны сделать проверку очереди пустым и удалить операцию атомарно, что можно сделать с помощью ключевого слова synchronized:

private class ThreadPool extends Thread {

    @Override
    public void run() {
        Runnable task;

        while (true) {
            synchronized(queue) {
                // give access to taskQueue to one thread at a time 
                if (!taskQueue.isEmpty()) {
                    task = taskQueue.remove();
                }
            }

            try {
                task.run();
            } catch (RuntimeException ex) {
                ex.printStackTrace();
            }
        }
    }
}

почему иногда оно выполняется без ошибок?

Потому чтопо своей природе планировщик потоков JVM: иногда он планирует выполнение потоков таким образом, чтобы они самостоятельно синхронно обращались к taskQueue.Но когда вы имеете дело с многопоточностью, вы не можете полагаться на порядок выполнения потоков и должны самостоятельно синхронизировать доступ к общим объектам.

0 голосов
/ 23 сентября 2018

Между проверкой while и фактическим удалением очередь может быть изменена другим потоком, что может привести к указанной вами ошибке.Это называется «racencondition».

Итак, чтобы исправить это, вам понадобится способ заблокировать доступ к очереди другими потоками, либо «заблокировав», используя «синхронизированный» блок собщий lock_object.Или просто путем «опроса» вместо удаления.

...