【问题标题】:How to remove elements from a queue in Java with a loop如何使用循环从Java中的队列中删除元素
【发布时间】:2014-12-16 05:26:13
【问题描述】:

我有这样的数据结构:

BlockingQueue 邮箱 = new LinkedBlockingQueue();

我正在尝试这样做:

for(Mail mail: mailbox)
{
    if(badNews(mail))
    {
        mailbox.remove(mail);
    }
}

显然循环的内容会干扰边界并触发错误,所以我通常会这样做:

for(int i = 0;  i < mailbox.size(); i++)
{
    if(badNews(mailbox.get(i)))
    {
        mailbox.remove(i);
        i--;
    }
}

但遗憾的是,BlockingQueue 没有按索引获取或删除元素的功能,所以我被卡住了。有什么想法吗?

编辑 - 一些澄清: 我的目标之一是保持相同的顺序,因此从头部弹出并将其放回尾部是不好的。另外,虽然没有其他线程会从邮箱中删除邮件,但他们会添加到邮箱中,所以我不想在删除算法的中间,有人给我发邮件,然后发生异常。 em>

提前致谢!

【问题讨论】:

  • 检查我发布的答案..

标签: java loops blockingqueue


【解决方案1】:

您可以根据需要轻松实现队列。如果提供的 API 没有此类功能,您将需要这样做。

一个喜欢:

import java.util.Iterator;
import java.util.LinkedList;


class Mail {
    boolean badMail;
}

class MailQueue {
    private LinkedList<Mail> backingQueue = new LinkedList<>();
    private final Object lock = new Object();

    public void push(Mail mail){
        synchronized (lock) {
            backingQueue.addLast(mail);
            if(backingQueue.size() == 1){
                // this is only element in queue, i.e. queue was empty before, so invoke if any thread waiting for mails in queue.
                lock.notify();
            }
        }
    }

    public Mail pop() throws InterruptedException{
        synchronized (lock) {
            while(backingQueue.isEmpty()){
                // no elements in queue, wait.
                lock.wait();
            }
            return backingQueue.removeFirst();
        }
    }

    public boolean removeBadMailsInstantly() {
        synchronized (lock) {
            boolean removed = false;
            Iterator<Mail> iterator = backingQueue.iterator();

            while(iterator.hasNext()){
                Mail mail = iterator.next();
                if(mail.badMail){
                    iterator.remove();
                    removed = true;
                }
            }

            return removed;
        }
    }
}

实现的队列将是线程安全的,无论是推送还是弹出。您还可以编辑队列以进行更多操作。并且它将允许通过多个线程(线程安全)访问removeBadMailsInstantly 方法。您还将学习多线程的概念。

【讨论】:

  • 如果有很多邮件都使用这个类和'private final Object lock = new Object();',它们会互相阻塞还是每个锁都是唯一的?
  • @Josh 基本上你要创建这个MailQueue的一个实例,并让所有线程使用这个实例,所以只会有一个锁,所有线程使用同一个锁。
  • @Josh 添加到队列的线程永远不会阻塞。仅当邮件队列为空时,从队列中检索邮件的线程才会阻塞。删除不良邮件的线程永远不会阻塞。您可以添加阻塞/非阻塞所需的方法。
【解决方案2】:

我查看了发布的解决方案,我认为我找到了一个适合我目的的版本。你觉得这个怎么样?

int size = mailbox.size();
for(int i = 0; i < size; i++)
{
    Mail currentMail = mailbox.poll();
    if (!badNews(currentMail))
        mailbox.offer(currentMail);
}

编辑:可能没有问题的新解决方案。大家觉得呢?

while(true)
{
    boolean badNewRemains = false;

    for(Mail mail: mailbox)
    {
        if(badNews(mail))
        {
            badNewRemains = true;
            mailbox.remove(mail);
            break;
        }
    }

    if(!badNewRemains)
        break;
}

【讨论】:

  • 队列的大小可能会改变,因为其他线程正在向队列添加更多邮件。这点要小心。
  • 是的,现在我看到如果新数据到达中间,元素可能会乱序。我在上面又试了一次。
【解决方案3】:

您可以对队列中的所有元素使用 p̶o̶p̶poll 和 p̶u̶s̶h̶offer,直到您在队列中完成一个完整的循环。这是一个例子:

Mail firstMail = mailbox.peek();
Mail currentMail = mailbox.pop();
while (true) {
    //a base condition to stop the loop
    Mail tempMail = mailbox.peek();
    if (tempMail == null || tempMail.equals(firstMail)) {
        mailbox.offer(currentMail);
        break;
    }
    //if there's nothing wrong with the current mail, then re add to mailbox
    if (!badNews(currentMail)) {
        mailbox.offer(currentMail);
    }
    currentMail = mailbox.poll();
}

请注意,此方法仅在此代码在单个线程中执行并且没有其他线程从该队列中删除项目时才有效。

也许您需要检查您是否真的要轮询或从 BlockingQueue 中获取元素。报价和认沽类似。

更多信息:


另一种错误较少的方法是使用临时集合,不一定是并发的,并将您仍然需要的元素存储在队列中。这是一个启动示例:

List<Mail> mailListTemp = new ArrayList<>();
while (mailbox.peek() != null) {
    Mail mail = mailbox.take();
    if (!badNews(mail)) {
        mailListTemp.add(mail);
    }
}
for (Mail mail : mailListTemp) {
    mailbox.offer(mail);
}

【讨论】:

  • @ScaryWombat 已修复。尽管如此,如果其他线程也从队列中删除项目,OP 不会通知我们。
  • @ScaryWombat 很好,我没有阅读任何其他 prev 问题,所以我缺乏这个上下文。
  • 也许我错过了你的观点,但如果队列为空(非阻塞),peek 不会返回 null 吗?在您之前的版本中,如果队列在进入循环之前为空,则会引发 NPE。
  • @Josh 如果这些是你的条件,那么是的,它会的。但你仍然必须对其进行测试。
  • @ScaryWombat 在您发表评论后审查了代码并修复了我的大错误。没有peek 等效方法,但在检查源代码后,它会在检索(但不删除)队列中的头元素之前应用ReentrantLock
猜你喜欢
  • 1970-01-01
  • 2021-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
相关资源
最近更新 更多