我目前有两个队列和物品在他们之间旅行。最初,一个项目被放入firstQueue
,然后三个专用线程中的一个将其移动到secondQueue
,最后另一个专用线程将其删除。这些举措显然包括一些处理。我需要能够获取任何项目的状态(IN_FIRST
,AFTER_FIRST
,IN_SECOND
,AFTER_SECOND
或ABSENT
),我手动执行更新队列被修改的statusMap
,如
while (true) {
Item i = firstQueue.take();
statusMap.put(i, AFTER_FIRST);
process(i);
secondQueue.add(i);
statusMap.put(i, IN_SECOND);
}
这很有效,但它很丑陋并留下了状态不一致的时间窗口。这种不一致并不是什么大问题,并且它可以通过同步解决,但这可能适得其反,因为队列容量有限并且可能会阻塞。丑陋使我更加困扰。
效率几乎不重要,因为处理需要几秒钟。专用线程用于控制并发。任何项目都不应该处于多个状态(但这不是非常重要,并且不能通过我目前的流行方法保证)。会有更多队列(和状态),他们会有不同类型(DelayQueue
,ArrayBlockingQueue
和PriorityQueue
)。
我想知道是否有一个可以推广到多个队列的好解决方案?
答案 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)
我发现您的模型在一致性,状态控制和缩放方面可能会有所改进。
实现这一目标的一种方法是将项目存入您的州,将这对夫妇入队并出列,并创建一种机制来确保状态发生变化。
我的提议可以在下图中看到:
根据这个模型和你的例子,我们可以这样做:
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
是一个包含Item
和State
的不可变对象。这可以确保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)(最坏情况)。
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更新:重新设计
概括这个项目,我可以看出状态机是这样的:
通过这种方式,我将队列中的工作者分离,以改进概念。我使用MemoryRep来保留整个过程中项目的唯一引用。 当然,如果您需要在物理存储库中保留ItemState,则可以使用基于事件的策略。
这保留了以前的想法,并为概念创造了更多的易读性。见:
据我所知,每个作业都有两个队列(输入/输出)和与商业模式的关系! 研究人员将始终找到最新且一致的项目状态。
所以,回答你的问题:
我可以使用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
可以使用每个队列。您将队列包裹在一个对象中,这样当您将一个项目出列时,就会发生一些事情
一旦process(i);
完成,您的代码将向包装器指示收据确认,包装器将从集合中删除该项并使布尔值为false。
返回状态的方法只会检查项目所在的队列或设置。
请注意,这至少会给出一次&#34;交付,意味着项目将至少处理一次,但如果处理时间太接近超时,可能不止一次。