java - 如何防止我的消费者线程两次删除最后一个元素?

标签 java multithreading nosuchelementexception

问题:

  1. Why do I get a NoSuchElementException when trying to remove the last element?
  2. How can I fix that?

我有 3 个类(见下文),用于向 LinkedList 添加/删除整数。 一切工作正常,直到删除线程到达最后一个元素。

似乎两个线程都试图删除它。第一个成功了,第二个就不行了。 但我认为同步方法/同步属性+ !sharedList.isEmpty() 可以处理这个问题。

类(class)制作人: 这个类应该创建随机数,将它们放入 sharedList 中,向控制台写入它刚刚添加了一个数字,并在被中断时停止。该类预计只有 1 个线程。

import java.util.LinkedList;

    public class Producer extends Thread
    {

        private LinkedList sharedList;
        private String name;

        public Producer(String name, LinkedList sharedList)
        {
            this.name = name;
            this.sharedList = sharedList;
        }

        public void run()
        {
            while(!this.isInterrupted())
            {
                while(sharedList.size() < 100)
                {
                    if(this.isInterrupted())
                    {
                        break;
                    } else 
                    {
                        addElementToList();
                    }
                }
            }
        }

        private synchronized void addElementToList() 
        {
            synchronized(sharedList)
            {
                sharedList.add((int)(Math.random()*100));
                System.out.println("Thread " + this.name + ": " + sharedList.getLast() + " added");
            }
            try {
                sleep(300);
            } catch (InterruptedException e) {
                this.interrupt();
            }
        }
    }

Class Consumer:此类应该删除sharedList 中的第一个元素(如果存在)。执行应该继续(在被中断之后)直到sharedList为空。此类需要多个(至少 2 个)线程。

import java.util.LinkedList;

public class Consumer extends Thread
{
    private String name;
    private LinkedList sharedList;

    public Consumer(String name, LinkedList sharedList)
    {
        this.name = name;
        this.sharedList = sharedList;
    }

    public void run()
    {
        while(!this.isInterrupted())
        {
            while(!sharedList.isEmpty())
            {
                removeListElement();
            }
        }
    }

    private synchronized void removeListElement()
    {
        synchronized(sharedList)
        {
            int removedItem = (Integer) (sharedList.element());
            sharedList.remove();
            System.out.println("Thread " + this.name + ": " + removedItem + " removed");
        }
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            this.interrupt();
        }
    }
}

Class MainMethod: 这个类应该启动和中断线程。

import java.util.LinkedList;


public class MainMethod 
{

    public static void main(String[] args) throws InterruptedException 
    {
        LinkedList sharedList = new LinkedList();
        Producer producer = new Producer("producer", sharedList);
        producer.start();
        Thread.sleep(1000);
        Consumer consumer1 = new Consumer("consumer1", sharedList);
        Consumer consumer2 = new Consumer("consumer2", sharedList);
        consumer1.start();
        consumer2.start();
        Thread.sleep(10000);
        producer.interrupt();
        consumer1.interrupt();
        consumer2.interrupt();
    }

}

异常:这正是我得到的异常。

Exception in thread "Thread-2" java.util.NoSuchElementException at java.util.LinkedList.getFirst(LinkedList.java:126) at java.util.LinkedList.element(LinkedList.java:476) at Consumer.removeListElement(Consumer.java:29) at Consumer.run(Consumer.java:20)

最佳答案

你的异常很容易解释。在

        while(!sharedList.isEmpty())
        {
            removeListElement();
        }

sharedList.isEmpty() 发生在同步之外,因此一个使用者仍然可以将列表视为空,而另一个使用者已经获取了最后一个元素。

错误地认为它是空的消费者不会尝试删除不再存在的元素,从而导致崩溃。

如果您想使用LinkedList使其线程安全,您必须执行每个原子读/写操作。例如。

while(!this.isInterrupted())
{
    if (!removeListElementIfPossible())
    {
        break;
    }
}

// method does not need to be synchronized - no thread besides this one is
// accessing it. Other threads have their "own" method. Would make a difference
// if this method was static, i.e. shared between threads.
private boolean removeListElementIfPossible()
{
    synchronized(sharedList)
    {
        // within synchronized so we can be sure that checking emptyness + removal happens atomic
        if (!sharedList.isEmpty())
        {
            int removedItem = (Integer) (sharedList.element());
            sharedList.remove();
            System.out.println("Thread " + this.name + ": " + removedItem + " removed");
        } else {
            // unable to remove an element because list was empty
            return false;
        }
    }
    try {
        sleep(1000);
    } catch (InterruptedException e) {
        this.interrupt();
    }
    // an element was removed
    return true;
}

您的生产商也存在同样的问题。但他们只会创建第 110 个元素或类似的东西。

解决您的问题的一个好方法是使用BlockingQueue。请参阅documentation举个例子。队列为您完成所有阻塞和同步,因此您的代码不必担心。

编辑:关于2个while循环:您不必使用2个循环,1个循环就足够了,但您会遇到另一个问题:消费者可能会在生产者填充队列之前将其视为空的。因此,您要么必须确保队列中存在某些内容,然后才能使用它,要么必须以其他方式手动停止线程。启动生产者后 thread.sleep(1000) 应该相当安全,但线程即使在 1 秒后也不能保证运行。使用例如一个 CountDownLatch 使其真正安全。

关于java - 如何防止我的消费者线程两次删除最后一个元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21825629/

相关文章:

java - 如何对导航和内容之间的交互进行编程

c++ - 在 while 循环中将线程置于另一个线程中

multithreading - pthreads v。SSE弱内存排序

java - 调用 .next() 方法时扫描仪出现 NoSuchElementException

java - 扫描仪 - java.util.NoSuchElementException

java - 是什么导致了 java.lang.ArrayIndexOutOfBoundsException 以及如何防止它?

java - Java 1.6 中的日历 - 奇怪的结果

java - 为什么我可以使用 US Ascii Locale 来处理大写/小写的德语变音符号?

Java - ExecutorService 有最大尺寸

java.util.NoSuchElementException错误,查找了可能的原因,仍然无法修复