首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ArrayBlockingQueue NoSuchElementException

ArrayBlockingQueue NoSuchElementException
EN

Stack Overflow用户
提问于 2018-09-23 07:10:10
回答 3查看 457关注 0票数 1

为了学习,我编写了以下代码,用于自定义线程池引用和编辑显示的代码( 这里。 )

如代码所示,我对任务队列使用了ArrayBlockingQueue。

代码:

代码语言:javascript
复制
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);
        }
    }
}

这段代码有时运行良好,有时会出错。

成功产出:

代码语言:javascript
复制
Do  Task 2 -> Thread-2
Wait for sometime: -> Thread-8
Do  Task 3 -> Thread-6
Do  Task 4 -> Thread-7

失败输出:

代码语言:javascript
复制
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()))执行队列空检查。代码有什么问题,为什么有时运行时没有错误?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-09-23 07:18:43

在“while”检查和实际删除之间,队列可能被另一个线程修改,可能会导致您提到的错误。这叫做“种族状况”。

因此,为了解决这个问题,您需要一种通过其他线程阻止对队列的访问的方法,要么是“锁定”,要么是使用具有共享锁对象的“同步”块。或者只是通过‘轮询’而不是删除。

票数 0
EN

Stack Overflow用户

发布于 2018-09-23 07:27:18

BlockingQueue只在单个操作级别上是线程安全的,我在代码中看到了check-then-act操作,它是一个复合操作,不是线程安全的。要使此代码线程安全,请在同步块内执行check-then-act并锁定队列本身。

代码语言:javascript
复制
synchronized(taskQueue) {
       while (!taskQueue.isEmpty()) {
             task = taskQueue.remove();
             task.run();
 }};

优化:如果任务耗时,可以在同步块之外执行它。这样其他线程就不必等到当前任务完成。

票数 0
EN

Stack Overflow用户

发布于 2018-09-23 07:45:30

代码有什么问题?

您可以在没有同步的情况下从多个线程访问taskQueue字段。您必须执行队列空检查和原子删除操作,这可以使用synchronized关键字完成:

代码语言:javascript
复制
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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52463859

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档