问题:
- Why do I get a NoSuchElementException when trying to remove the last element?
- 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/