简体   繁体   English

弱一致性ConcurrentSkipListSet的含义

[英]Implications of weakly consistent ConcurrentSkipListSet

Using a ConcurrentSkipListSet I have observed some wired behaviour, that I suspect is caused by the weakly consistency of the concurrent set. 通过使用ConcurrentSkipListSet我观察到一些有线行为,我怀疑这是由并发集的弱一致性引起的。

The JavaDoc has this to say on that topic: JavaDoc在该主题上有这样的说法:

Most concurrent Collection implementations (including most Queues) also differ from the usual java.util conventions in that their Iterators and Spliterators provide weakly consistent rather than fast-fail traversal: 大多数并发的Collection实现(包括大多数Queue)也与通常的java.util约定不同,它们的迭代器和拆分器提供了弱一致性而不是快速失败遍历:

  • they may proceed concurrently with other operations 他们可能会与其他操作同时进行
  • they will never throw ConcurrentModificationException 他们永远不会抛出ConcurrentModificationException
  • they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction. 它们被保证可以遍历在构造时已经存在的元素一次,并且可以(但不保证)反映出构造后的任何修改。

This is the code that I use: 这是我使用的代码:

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());
}

At occasion I can see log output like this: 有时我会看到这样的日志输出:

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 

These are two almost consecutive log lines. 这是两条几乎连续的日志行。 Please note that they are executed on different threads. 请注意,它们在不同的线程上执行。 The TimedTask entry that is added is not listed in the second log line. 第二个日志行中未列出添加的TimedTask条目。

Am I correct in my assumption that this is due to the weakly consistency? 我是否认为这是由于弱一致性导致的? If so, would this also imply that the iterator.next() retrieves a different entry than iterator.remove() deletes? 如果是这样,这是否也意味着iterator.next()检索的条目与iterator.remove()删除的条目不同?

What I am observing, is that this added entry is never processed and does not show up in the concurrent set at any time. 我观察到的是,此添加的条目从未被处理过,并且在任何时候都不会显示在并发集中。

What would be a good solution to avoid this? 有什么好的方法可以避免这种情况? What comes to my mind, is create a copy of the set and iterate over that one, as it is acceptable, that entries can be processed in a future iteration, as long as they are processed. 我想到的是创建该集合的副本,并在该集合上进行迭代,这是可以接受的,只要条目可以处理,就可以在以后的迭代中对其进行处理。 Looking at Weakly consistent iterator by ConcurrentHashMap suggests the iteration already happens on a copy of the set, so this might not change anything. 查看ConcurrentHashMap的弱一致性迭代器,表明迭代已经在集合的副本上进行,因此这可能不会有任何改变。

EDIT Sample implementation of a TimedTask : 编辑 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()+"}";
    }
}

As @BenManes pointed out in his comment, the issue is with the Comparator used. 正如@BenManes在其评论中指出的那样,问题出在所使用的比较器。 When the result of the Comparator is 0, even through the two tasks are not equal, entries will be overridden. 当比较器的结果为0时,即使通过两个任务不相等,条目也将被覆盖。 In effect, the Comparator should consider the same fields as hashCode and equals . 实际上,比较器应考虑与hashCodeequals相同的字段。 Use a Comparator implementation like this: 使用这样的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;
}

With an implementation like this the comparison is based on the execution time and when both of them are the same (comparison is 0) ensure they are ordered based on their UUID. 对于这样的实现,比较基于执行时间,并且当两者相同时(比较为0),请确保根据其UUID对其进行排序。

For the use case the order of tasks with the same execution time is not relevant. 对于用例,具有相同执行时间的任务顺序无关紧要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ConcurrentSkipListSet 如何具有弱一致性的迭代器? 理解“弱一致性”的含义 - How does ConcurrentSkipListSet has Iterators that are weakly consistent? Understanding meaning of 'weakly consistent' ConcurrentHashMap的弱一致性迭代器 - Weakly consistent iterator by ConcurrentHashMap java中的迭代器类型(弱一致) - Iterator type in java (weakly consistent) ConcurrentNavigableMap,解释弱一致的迭代器 - ConcurrentNavigableMap, interpretation of weakly consistent iterators 失败的安全迭代器和弱一致的迭代器 - fail safe iterators and weakly consistent iterators ConcurrentHashMap返回一个弱一致的迭代器,为什么我们应该使用它呢? - ConcurrentHashMap returns a weakly consistent iterator, why should we use it anyhow? ConcurrentSkipList? 也就是说,不是ConcurrentSkipListSet - ConcurrentSkipList? That is, not a ConcurrentSkipListSet 弱一致性迭代器在对hasNext方法的响应为true之后是否可以引发NoSuchElementException? - Can a weakly consistent iterator throw NoSuchElementException after it has responded true to hasNext method? ConcurrentSkipListSet是静态的吗? - Is ConcurrentSkipListSet static? 何时ConcurrentSkipListSet有用? - When is a ConcurrentSkipListSet useful?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM