![](/img/trans.png)
[英]How do I prevent Jsoup from removing 'href' attribute of anchor element?
[英]How do i prevent my consumer-threads from removing the last element twice?
问题:
- 尝试删除最后一个元素时,为什么会出现NoSuchElementException?
- 我该如何解决?
我有3个类(请参见下文)将整数添加/删除到LinkedList。 一切正常,直到删除线程到达最后一个元素。
似乎两个线程都试图将其删除。 第一个成功,第二个不成功。 但是我认为!sharedList.isEmpty()
-method / synchroniced-attribute + !sharedList.isEmpty()
可以解决这个问题。
类生产者:该类应该创建随机数,将其放入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();
}
}
}
类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();
}
}
}
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();
}
}
例外:这是我得到的确切例外。
Consumer.removeListElement(Consumer)上java.util.LinkedList.element(LinkedList.java:476)处java.util.LinkedList.getFirst(LinkedList.java:126)处的线程“ Thread-2”中的java.util.NoSuchElementException。 Java:29)在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
。 有关示例,请参见文档 。 队列为您完成了所有阻止和同步操作,因此您的代码不必担心。
编辑:关于2个while循环:您不必使用2个循环,1个循环就足够了,但是您会遇到另一个问题:在生产者填充队列之前,消费者可能会看到队列为空。 因此,要么必须确保队列中有某些内容才可以使用它,要么必须以其他方式手动停止线程。 启动生产程序后,您的thread.sleep(1000)
应该相当安全,但是即使在1秒后也不能保证线程正在运行。 使用例如CountDownLatch
使其真正安全。
我想知道为什么您不使用Java提供的现有类。 我使用这些代码重写了您的程序,它变得更短,更易于阅读。 另外,缺少synchronized
会阻止所有线程(除了获得锁的线程(您甚至执行双重同步))之外,它使程序实际上可以并行运行。
这是代码:
制片人:
public class Producer implements Runnable {
protected final String name;
protected final LinkedBlockingQueue<Integer> sharedList;
protected final Random random = new Random();
public Producer(final String name, final LinkedBlockingQueue<Integer> sharedList) {
this.name = name;
this.sharedList = sharedList;
}
public void run() {
try {
while (Thread.interrupted() == false) {
final int number = random.nextInt(100);
sharedList.put(number);
System.out.println("Thread " + this.name + ": " + number);
Thread.sleep(100);
}
} catch (InterruptedException e) {
}
}
}
消费者:
public class Consumer implements Runnable {
protected final String name;
protected final LinkedBlockingQueue<Integer> sharedList;
public Consumer(final String name, final LinkedBlockingQueue<Integer> sharedList) {
this.name = name;
this.sharedList = sharedList;
}
public void run() {
try {
while (Thread.interrupted() == false) {
final int number = sharedList.take();
System.out.println("Thread " + name + ": " + number + " taken.");
Thread.sleep(100);
}
} catch (InterruptedException e) {
}
}
}
主要:
public static void main(String[] args) throws InterruptedException {
final LinkedBlockingQueue<Integer> sharedList = new LinkedBlockingQueue<>(100);
final ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(new Producer("producer", sharedList));
Thread.sleep(1000);
executor.execute(new Consumer("consumer1", sharedList));
executor.execute(new Consumer("consumer2", sharedList));
Thread.sleep(1000);
executor.shutdownNow();
}
有几个区别:
由于我使用并发列表,因此不必太在乎同步,列表在内部进行。
由于此列表使用原子锁定而不是通过synchronized
进行真正的阻止,因此使用更多线程将更好地扩展。
我确实将阻塞队列的限制设置为100,因此即使生产者中没有检查,列表中也不会有超过100个元素,因为如果达到限制, put
将会阻塞。
我使用random.nextInt(100)
,它是您使用的便捷函数,由于用法更清晰,因此产生的错字少很多。
Producer和Consumer都是Runnable,因为这是Java中线程化的首选方式。 这样,以后就可以将任何形式的线程包装在它们周围以执行,而不仅仅是原始的Thread类。
我使用ExecutorService而不是Thread,它可以更轻松地控制多个线程。 线程创建,调度和其他处理在内部完成,因此我要做的就是选择最合适的ExecutorService并在完成后调用shutdownNow()
。
还要注意,没有必要将InterruptedException扔到void中。 如果消费者/生产者被打断,则这是尽快优雅地停止执行的信号。 除非我需要通知其他“线程”背后的人,否则无需再次抛出该Exception(尽管也不会造成任何伤害)。
我使用关键字final
来标注以后不会更改的元素。 这是一次提示,提示编译器可以进行一些优化,同时也有助于防止意外更改不应更改的变量。 通过不允许在线程环境中更改变量可以避免很多问题,因为线程问题几乎总是需要同时读取和写入某些内容。 如果您不能写作,这些事情就不会发生。
花一些时间在Java库中搜索最适合您问题的类,通常可以解决很多麻烦,并且可以大大减少代码的大小。
尝试切换的地方
while(!sharedList.isEmpty())
和
synchronized(sharedList)
我认为您不需要在removeListElement()上进行同步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.