这个问题的一个简单但不是很完美的解决方案是在一个数组中维护一组子队列,该数组等于您正在运行的处理线程的数量。单个主线程将项目从单个主队列中拉出,并将它们添加到通过对象键的 hashCode 模数索引的子队列中(识别和关联您的任务的 hashCode)。
例如
int queueIndex = myEntity.getKey().hashCode() % queues.length;
只有一个线程处理该队列,并且同一实体的所有任务都将提交到该队列,因此不会出现竞争条件。
这个解决方案是不完美的,因为一些线程最终可能会比其他线程更大。实际上,这不太重要,但需要考虑。
简单解决方案的问题:
将项目从单个队列中拉出然后锁定受影响实体的不同内容的更简单解决方案具有竞争条件(正如 Aurand 指出的那样)。鉴于:
Master Queue [ Task1(entity1), Task2(entity1), ... ]
wheretask1
和task2
都编辑同一个实体entity1
,并且队列上存在thread1
和thread2
操作,那么预期/期望的事件序列是:
- Thread1 接受 task1
- Thread1 锁定在 entity1 上
- Thread1 编辑 entity1
- Thread1 解锁 entity1
- Thread2 接受 task2
- Thread2 锁定 entity1
- Thread2 编辑 entity1
- Thread2 解锁 entity1
不幸的是,即使锁是线程的 run 方法的第一条语句,也有可能发生以下序列:
- Thread1 接受 task1
- Thread2 接受 task2
- Thread2 锁定 entity1
- Thread2 编辑 entity1
- Thread2 解锁 entity1
- Thread1 锁定 entity1
- Thread1 编辑 entity1
- Thread1 解锁 entity1
为了避免这种情况,每个线程在从队列中获取任务之前必须锁定某些东西(比如队列),然后在仍然持有父锁的同时获取实体上的锁。但是,您不想在持有此父锁并等待获取实体锁时阻塞所有内容,因此您只需尝试获取实体锁,然后在获取失败时处理(可能将其放入另一个队列) . 总体而言,情况变得不平凡。