跟踪地图中队列之间的进度

时间:2018-05-31 13:10:05

标签: java concurrency queue concurrenthashmap

我目前有两个队列和物品在他们之间旅行。最初,一个项目被放入firstQueue,然后三个专用线程中的一个将其移动到secondQueue,最后另一个专用线程将其删除。这些举措显然包括一些处理。我需要能够获取任何项目的状态(IN_FIRSTAFTER_FIRSTIN_SECONDAFTER_SECONDABSENT),我手动执行更新队列被修改的statusMap,如

while (true) {
    Item i = firstQueue.take();
    statusMap.put(i, AFTER_FIRST);
    process(i);
    secondQueue.add(i);
    statusMap.put(i, IN_SECOND);
}

这很有效,但它很丑陋并留下了状态不一致的时间窗口。这种不一致并不是什么大问题,并且它可以通过同步解决,但这可能适得其反,因为队列容量有限并且可能会阻塞。丑陋使我​​更加困扰。

效率几乎不重要,因为处理需要几秒钟。专用线程用于控制并发。任何项目都不应该处于多个状态(但这不是非常重要,并且不能通过我目前的流行方法保证)。会有更多队列(和状态),他们会有不同类型(DelayQueueArrayBlockingQueuePriorityQueue)。

我想知道是否有一个可以推广到多个队列的好解决方案?

4 个答案:

答案 0 :(得分:3)

用逻辑包装队列以管理Item状态是否有意义?

public class QueueWrapper<E> implements BlockingQueue<E> {
    private Queue<E> myQueue = new LinkedBlockingQueue<>();
    private Map<E, Status> statusMap;

    public QueueWrapper(Map<E, Status> statusMap) {
        this.statusMap = statusMap;
    }

    [...]
    @Override
    public E take() throws InterruptedException {
        E result = myQueue.take();
        statusMap.put(result, Status.AFTER_FIRST);
        return result;
    }

这样,状态管理始终与队列操作相关(并包含在其中......)

显然statusMap需要同步,但无论如何这都是一个问题。

答案 1 :(得分:3)

我发现您的模型在一致性,状态控制和缩放方面可能会有所改进。

实现这一目标的一种方法是将项目存入您的州,将这对夫妇入队并出列,并创建一种机制来确保状态发生变化。

我的提议可以在下图中看到:

enter image description here

根据这个模型和你的例子,我们可以这样做:

package stackoverflow;

import java.util.concurrent.LinkedBlockingQueue;

import stackoverflow.item.ItemState;
import stackoverflow.task.CreatingTask;
import stackoverflow.task.FirstMovingTask;
import stackoverflow.task.SecondMovingTask;

public class Main {

    private static void startTask(String name, Runnable r){
        Thread t = new Thread(r, name);
        t.start();
    }

    public static void main(String[] args) {
        //create queues
        LinkedBlockingQueue<ItemState> firstQueue = new LinkedBlockingQueue<ItemState>();
        LinkedBlockingQueue<ItemState> secondQueue = new LinkedBlockingQueue<ItemState>();
        //start three threads
        startTask("Thread#1", new CreatingTask(firstQueue));
        startTask("Thread#2", new FirstMovingTask(firstQueue, secondQueue));
        startTask("Thread#3", new SecondMovingTask(secondQueue));
    }
}

每个任务根据以下 ItemState 的确认运行op()的操作:

  

三个专用线程中的一个将它移动到secondQueue,最后   另一个专用线程将其删除。

ItemState是一个包含ItemState的不可变对象。这可以确保Item和State值之间的一致性。

ItemState确认了创建自控状态机制的下一个状态:

public class FirstMovingTask {
    //others codes
    protected void op() {
            try {
                //dequeue
                ItemState is0 = new ItemState(firstQueue.take());
                System.out.println("Item " + is0.getItem().getValue() + ": " + is0.getState().getValue());
                //process here
                //enqueue
                ItemState is1 = new ItemState(is0);
                secondQueue.add(is1);
                System.out.println("Item " + is1.getItem().getValue() + ": " + is1.getState().getValue());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    //others codes
}

使用ItemState实现:

public class ItemStateImpl implements ItemState {
    private final Item item;
    private final State state;

    public ItemStateImpl(Item i){
        this.item = i;
        this.state = new State();
    }

    public ItemStateImpl(ItemState is) {
        this.item = is.getItem();
        this.state = is.getState().next();
    }

    // gets attrs
}

因此,这种方式可以构建更优雅,灵活和可扩展的解决方案。 可扩展,因为您可以控制更多状态,仅更改next()并概括移动任务以增加队列数。

结果:

Item 0: AFTER_FIRST
Item 0: IN_FIRST
Item 0: IN_SECOND
Item 0: AFTER_SECOND
Item 1: IN_FIRST
Item 1: AFTER_FIRST
Item 1: IN_SECOND
Item 1: AFTER_SECOND
Item 2: IN_FIRST
Item 2: AFTER_FIRST
Item 2: IN_SECOND
... others

更新(06/07/2018):分析使用地图进行搜索 使用像比较器这样的等值来搜索映射可能不起作用,因为通常值和身份(密钥/哈希)之间的映射不是一对一的(参见下图)。这样就需要为搜索值创建一个排序列表,这会导致O(n)(最坏情况)。

enter image description here

Item.getValuesHashCode()

private int getValuesHashCode(){
  return new HashCodeBuilder().append(value).hashCode();
}

在这种情况下,您必须保留Vector<ItemState>而不是Item并使用密钥,例如getValuesHashCode的结果。改变状态控制机制,保持项目的首次参考和状态电流。见下文:

//Main.class
public static void main(String[] args) {
    ... others code ...

    //references repository
    ConcurrentHashMap<Integer, Vector<ItemState>> statesMap = new ConcurrentHashMap<Integer, Vector<ItemState>>();
    //start three threads
    startTask("Thread#1", new CreatingTask(firstQueue, statesMap));

    ... others code ...
}

//CreateTask.class
protected void op() throws InterruptedException {
    //create item
    ItemState is = new ItemStateImpl(new Item(i++, NameGenerator.name()));
    //put in monitor and enqueue
    int key = is.getHashValue();
    Vector<ItemState> items = map.get(key);
    if (items == null){
        items = new Vector<>();
        map.put(key, items);
    }
    items.add(is);
    //enqueue
    queue.put(is);
}

//FirstMovingTask.class
protected void op() throws InterruptedException{
    //dequeue
    ItemState is0 = firstQueue.take();
    //process
    ItemState is1 = process(is0.next());
    //enqueue 
    secondQueue.put(is1.next());
}

//ItemState.class
public ItemState next() {
    //required for consistent change state
    synchronized (state) {
        state = state.next();
        return this;
    }
}

要搜索,您必须使用concurrentMapRef.get(key)。结果将引用更新的ItemState。

我的测试结果:

# key = hash("a")
# concurrentMapRef.get(key)
...
Item#7#0    : a - IN_FIRST 
... many others lines
Item#7#0    : a - AFTER_FIRST 
Item#12#1   : a - IN_FIRST 
... many others lines
Item#7#0    : a - IN_SECOND 
Item#12#1   : a - IN_FIRST 
... many others lines
Item#7#0    : a - AFTER_SECOND 
Item#12#1   : a - IN_FIRST 

代码中的更多详细信息:https://github.com/ag-studies/stackoverflow-queue

于06/09/2018更新:重新设计

概括这个项目,我可以看出状态机是这样的:

enter image description here

通过这种方式,我将队列中的工作者分离,以改进概念。我使用MemoryRep来保留整个过程中项目的唯一引用。 当然,如果您需要在物理存储库中保留ItemState,则可以使用基于事件的策略。

这保留了以前的想法,并为概念创造了更多的易读性。见:

enter image description here

据我所知,每个作业都有两个队列(输入/输出)和与商业模式的关系! 研究人员将始终找到最新且一致的项目状态

所以,回答你的问题:

  • 我可以使用MemoryRep(基本上是Map)在任何地方找到 Item 的一致状态,在 ItemState 中包装状态和项目,并控制更改状态排队或出列的工作。

  • 除了运行next()

  • 之外,性能得到了保证。
  • 状态总是一致的(针对您的问题)

  • 在此模型中,可以使用任何队列类型,任意数量的作业/队列以及任意数量的状态。

  • 另外这很漂亮!!

答案 2 :(得分:1)

如前所述,将队列或项目包裹起来是可行的解决方案,或两者兼而有之。

public class ItemWrapper<E> {
   E item;
   Status status;
   public ItemWrapper(Item i, Status s){ ... }
   public setStatus(Status s){ ... }
   // not necessary if you use a queue wrapper (see queue wrapper)
   public boolean equals(Object obj) {
     if ( obj instanceof ItemWrapper)
       return item.equals(((ItemWrapper) obj).item) 
     return false;
   }
   public int hashCode(){
     return item;
   }
}
...
process(item) // process update status in the item
...

可能已经回答的更好的方法是让QueueWrapper更新队列状态。为了好玩,我没有使用状态图,但我使用之前的itemwrapper看起来更干净(状态图也可以工作)。

public class QueueWrapper<E> implements Queue<E> {
  private Queue<ItemWrapper<E>> myQueue;
  static private Status inStatus; // FIRST
  static private Status outStatus; // AFTER_FIRST
  public QueueWrapper(Queue<E> myQueue, Status inStatus, Status outStatus) {...}
  @Override
  public boolean add(E e) {
    return myQueue.add(new ItemWrapper(e, inStatus));
  }
  @Override
  public E remove(){
    ItemWrapper<E> result = myQueue.remove();
    result.setStatus(outStatus)
    return result.item;
  }
  ...  
  }

您还可以使用AOP在队列中注入状态更新而不更改队列(状态图应该比itemwrapper更合适)。

也许我没有回答你的问题,因为一个简单的方法可以知道你的项目在哪里可以检查每个队列中的&#34;包含&#34;功能

答案 3 :(得分:1)

这里的内容与其他人所说的不同。从队列服务和系统的世界来看,我们有消息确认的概念。这很好,因为它还为您提供了一些内置的重试逻辑。

我将从高层开始说明它是如何工作的,如果你需要,我可以添加代码。

基本上,您有Set可以使用每个队列。您将队列包裹在一个对象中,这样当您将一个项目出列时,就会发生一些事情

  1. 该项目已从队列中删除
  2. 该项目已添加到关联的集合
  3. 计划任务(包含原子布尔值(默认为false)的lambda)。运行时,它将从集合中删除项目,如果布尔值为false,则将其放回队列
  4. 项目和布尔值周围的包装器将返回给调用者
  5. 一旦process(i);完成,您的代码将向包装器指示收据确认,包装器将从集合中删除该项并使布尔值为false。

    返回状态的方法只会检查项目所在的队列或设置。

    请注意,这至少会给出一次&#34;交付,意味着项目将至少处理一次,但如果处理时间太接近超时,可能不止一次。