【问题标题】:Unable to maintain order of producer tasks in java multithreading无法在java多线程中维护生产者任务的顺序
【发布时间】:2013-09-16 09:22:03
【问题描述】:

我正在编写一个多线程应用程序,其中有 n 个生产者试图将元素添加到共享资源。我想维护生产者在共享资源中生成元素的顺序。

例如,我的共享资源是一个 SynchronizedQueue,P1、P2、P3、P4 将按照 p1、p2、p3、p4 的顺序生成一个新元素,在此期间,P5 生产者将其元素添加到队列中,所以 P1、P2、P3、P4 将等待锁。一旦 P5 释放锁,P1-4 中的任何一个都将获得锁,因此我们松开了元素的顺序。

有没有办法保持等待锁的元素的顺序?据我了解,这是不可能的,但我想检查这是否可以通过编程方式实现。

【问题讨论】:

  • 你想总是先插入 p1,还是插入顺序无关紧要,只是最后的队列按顺序排列了元素 p1、p2、p3、p4 和 p5?
  • 你的意思是同步队列吗?如果没有,SynchronizedQueue 来自哪个库,或者如果是你自己的实现,我们可以看到代码吗?
  • 插入的顺序应该取决于生产者。假设顺序如下:生产者1 - p1 - t1,生产者3- p3 - t2,生产者4 - p4 - t3,生产者2 - p2 - t4,时间顺序为t1
  • 正确地说,这个问题与饥饿有关。我开始知道 java 中的重入锁具有可用于避免这种饥饿的公平参数。

标签: java multithreading producer


【解决方案1】:

您没有提供任何代码来查看如何在共享资源上获取/释放锁,但您可能对 java.util.concurrent.locks.ReentrantLock 类感兴趣。

这个类的构造函数接受一个可选的公平参数。 当设置为 true 时,在争用情况下,锁倾向于授予对 等待时间最长的线程。

所以如果P1,P2,P3依次尝试获取重入锁并且共享资源被锁定,那么等待时间最长的线程(这里是P1)会先获取锁,然后是P2,然后是P3 ,然后是之后的任何其他线程。

【讨论】:

  • 这种行为有保证吗?
  • locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order.
【解决方案2】:

如果您有一种方法可以为存储在队列中的元素分配值,这可以用于它们的排序,那么您可以使用PriorityBlockingQueue 而不是SynchronizedQueue。优先级队列的元素根据它们的自然顺序或在队列构建时提供的 Comparator 进行排序。

例如,您可以将生产者的 id 存储在元素中,并有一个 Comparator 知道如何比较生产者 id。

【讨论】:

  • 妈的你比我快一点!
  • 这不会阻止消费者在 p4 等之前消费 p5。
  • 我同意。此解决方案可以帮助保持队列中元素的顺序以及它们被使用的顺序。我认为这是预期的最终结果,但我可能错了
【解决方案3】:

我能想到的一种方法是创建一个包装类PriorityP,其中包含int priorityP value 字段。

然后您为每个线程分配一个优先级 (int),该线程将作为结果提供具有适当优先级和值的 PriorityP

现在,您可以使用PriorityBlockingQueue 代替SynchronizedQueue,并在PriorityP 类中实现Comparator 接口。

当你这样做时,每当一个线程将他的值输入队列时,它就会自动放在正确的位置。

【讨论】:

  • 这不会阻止消费者在 p4 等之前消费 p5。
  • 我认为这无关紧要。他的问题不是很清楚,但他没有提到任何地方的消费者和消费的顺序。他只希望队列中的对象按正确的顺序排列。
  • 他提到他想在保持秩序的同时将内容添加到消息队列等“共享资源”中。所以我认为这几乎需要消费顺序。
  • 当然可以,但问题是,他是在插入第一个元素时开始消费,还是在第一次访问队列之前所有线程都插入队列?这两种情况都是可能的,这就是为什么这个问题并不完全清楚。我假设所有东西都在被访问之前被插入到队列中。
【解决方案4】:

当然有可能 :) 通常这类问题是通过在已知序列的地方添加序列号来解决的。在您的情况下,您的生产商可以生产具有“P1-1”、“P1-2”、“P2-1”等序列号的产品,然后可以在经过一些处理后用于恢复订单。

这比维护顺序更可取,因为通过这种方式无法进行某些优化(例如,搜索“Java 重新排序”)。

你可以这样做:

PriorityQueue priorityQueue = // give suitable comparator
int seq = 0;

void synchronized add(E e){
    priorityQueue.add(e);
}

E synchronized poll(){
    E candidate = priorityQueue.peek();
    if(candidate.getSeq() == seq){
        seq++;
        return priorityQueue.poll();
    }else{
        log.debug("{} hasn't arrived yet", seq);
        return null;
    }
}

【讨论】:

    【解决方案5】:

    问题在于,获取锁的顺序可以与生成元素的顺序任意不同,甚至可以与多线程中生产者排队获取锁的顺序任意不同环境。所以你最终会遇到你试图解决的同样的问题。 (见recursion

    并非所有问题都可以通过另一个级别的间接来解决;-)

    【讨论】:

      【解决方案6】:

      这是一个错误的目标。我假设您的生产者是线程。线程执行顺序没有严格定义——一次线程 A 可以比线程 B 运行得更快,另一次 B 运行得更快,因此您要保留的顺序本身就是随机的,不值得关心。此外,将元素添加到队列所花费的时间是如此之少,以至于即使是两个或多个线程同时执行此操作,您也可以将其视为发生在同一时刻。

      多线程类似于相对论物理学 - 没有通用时间,只有发生之前的关系。如果产生的事件之间存在这种关系,那么应该使用这种关系。元素加入公共队列的时间不能作为这样的关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-17
        • 1970-01-01
        • 1970-01-01
        • 2014-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多