![](/img/trans.png)
[英]How does ConcurrentSkipListSet has Iterators that are weakly consistent? Understanding meaning of 'weakly consistent'
[英]Implications of weakly consistent ConcurrentSkipListSet
通过使用ConcurrentSkipListSet
我观察到一些有线行为,我怀疑这是由并发集的弱一致性引起的。
JavaDoc在该主题上有这样的说法:
大多数并发的Collection实现(包括大多数Queue)也与通常的java.util约定不同,它们的迭代器和拆分器提供了弱一致性而不是快速失败遍历:
- 他们可能会与其他操作同时进行
- 他们永远不会抛出ConcurrentModificationException
- 它们被保证可以遍历在构造时已经存在的元素一次,并且可以(但不保证)反映出构造后的任何修改。
这是我使用的代码:
private final ConcurrentSkipListSet<TimedTask> sortedEvents;
public TimedUpdatableTaskList(){
Comparator<TimedTask> comparator =
(task1, task2) -> task1.getExecutionTime().compareTo(task2.getExecutionTime());
sortedEvents = new ConcurrentSkipListSet<>(comparator);
}
public void add(TimedTask task) {
log.trace("Add task {}", task);
sortedEvents.add(task);
}
public void handleClockTick(ClockTick event) {
LocalDateTime now = date.getCurrentDate();
logContent("Task list BEFORE daily processing ("+now+")");
for (Iterator<TimedTask> iterator = sortedEvents.iterator(); iterator.hasNext();) {
TimedTask task = iterator.next();
Preconditions.checkNotNull(task.getExecutionTime(),
"The exectution time of the task may not be null");
if (task.getExecutionTime().isBefore(now)) {
log.trace("BEFORE: Execute task {} scheduled for {} on {}",
task, task.getExecutionTime(), now);
try {
task.run();
iterator.remove();
} catch (Exception e) {
log.error("Failed to execute timed task", e);
}
log.trace("AFTER: Execute task {} scheduled for {} on {}",
task, task.getExecutionTime(), now);
}
if (task.getExecutionTime().isAfter(now)) {
break; // List is sorted
}
}
logContent("Task list AFTER daily processing");
}
private void logContent(String prefix) {
StringBuilder sb = new StringBuilder();
sortedEvents.stream().forEach(task ->sb.append(task).append(" "));
log.trace(prefix + ": "+sb.toString());
}
有时我会看到这样的日志输出:
2018-05-19 13:46:00,453 [pool-3-thread-1] TRACE ... - Add task AIRefitTask{ship=Mercurius, scheduled for: 1350-07-16T08:45}
2018-05-19 13:46:00,505 [pool-3-thread-5] TRACE ... - Task list BEFORE daily processing (1350-07-16T09:45): AIRefitTask{ship=Tidewalker, scheduled for: 1350-07-16T08:45} AIRepairTask{ship=Hackepeter, scheduled for: 1350-07-16T13:45} ch.sahits.game.openpatrician.engine.event.task.WeaponConstructionTask@680da167 ch.sahits.game.openpatrician.engine.player.DailyPlayerUpdater@6e22f1ba AIRepairTask{ship=St. Bonivatius, scheduled for: 1350-07-17T03:45} AIRepairTask{ship=Hackepeter, scheduled for: 1350-07-17T05:45} ch.sahits.game.openpatrician.engine.event.task.WeeklyLoanerCheckTask@47571ace
这是两条几乎连续的日志行。 请注意,它们在不同的线程上执行。 第二个日志行中未列出添加的TimedTask
条目。
我是否认为这是由于弱一致性导致的? 如果是这样,这是否也意味着iterator.next()
检索的条目与iterator.remove()
删除的条目不同?
我观察到的是,此添加的条目从未被处理过,并且在任何时候都不会显示在并发集中。
有什么好的方法可以避免这种情况? 我想到的是创建该集合的副本,并在该集合上进行迭代,这是可以接受的,只要条目可以处理,就可以在以后的迭代中对其进行处理。 查看ConcurrentHashMap的弱一致性迭代器,表明迭代已经在集合的副本上进行,因此这可能不会有任何改变。
编辑 TimedTask
示例实现:
class AIRefitTask extends TimedTask {
private static final Logger LOGGER = LogManager.getLogger(AIRefitTask.class);
private AsyncEventBus clientServerEventBus;
private ShipWeaponsLocationFactory shipWeaponLocationFactory;
private ShipService shipService;
private final IShip ship;
private final EShipUpgrade level;
private final IShipyard shipyard;
public AIRefitTask(LocalDateTime executionTime, IShip ship, EShipUpgrade upgrade, IShipyard shipyard) {
super();
setExecutionTime(executionTime);
LOGGER.debug("Add AIRefitTask for {} to be done at {}", ship.getName(), executionTime);
this.ship = ship;
this.level = upgrade;
this.shipyard = shipyard;
}
@Override
public void run() {
EShipUpgrade currentLevel = ship.getShipUpgradeLevel();
while (currentLevel != level) {
ship.upgrade();
List<IWeaponSlot> oldWeaponSlots = ship.getWeaponSlots();
List<IWeaponSlot> newWeaponSlots = shipWeaponLocationFactory.getShipWeaponsLocation(ship.getShipType(), level);
ship.setWeaponSlots(newWeaponSlots);
for (IWeaponSlot slot : oldWeaponSlots) {
if (slot.getWeapon().isPresent()) {
EWeapon weapon = (EWeapon) slot.getWeapon().get();
if (slot instanceof SecondaryLargeWeaponSlot) {
if (!shipService.isLargeWeapon(weapon)) { // ignore large weapons in secondary slots
shipService.placeWeapon(weapon, ship);
}
} else {
// Not secondary slot
shipService.placeWeapon(weapon, ship);
}
}
}
currentLevel = ship.getShipUpgradeLevel();
}
ship.setAvailable(true);
shipyard.removeCompletedUpgrade(ship);
LOGGER.debug("Refited ship {}", ship.getName());
clientServerEventBus.post(new RefitFinishedEvent(ship));
}
@Override
public String toString() {
return "AIRefitTask{ship="+ship.getUuid()+", scheduled for: "+getExecutionTime()+"}";
}
}
正如@BenManes在其评论中指出的那样,问题出在所使用的比较器。 当比较器的结果为0时,即使通过两个任务不相等,条目也将被覆盖。 实际上,比较器应考虑与hashCode
和equals
相同的字段。 使用这样的Comparator
实现:
public int compare(TimedTask task1, TimedTask task2) {
int executionTimeBasedComparisonResult = task1.getExecutionTime().compareTo(task2.getExecutionTime());
if (executionTimeBasedComparisonResult == 0) { // two execution times are equal
return task1.getUuid().compareTo(task2.getUuid());
}
return executionTimeBasedComparisonResult;
}
对于这样的实现,比较基于执行时间,并且当两者相同时(比较为0),请确保根据其UUID对其进行排序。
对于用例,具有相同执行时间的任务顺序无关紧要。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.