3

我有一个游戏,它每隔 X 秒就会将内存中的更改值写回我的数据库。当它们保存的数据被编辑时,这些值存储在容器(HashMaps 和 ArrayLists)中。

为简单起见,假设我只有 1 个容器可以写入数据库:

public static HashMap<String, String> dbEntitiesDeletesBacklog = new HashMap<String, String>();

我的数据库写入循环:

Timer dbUpdateJob = new Timer();
dbUpdateJob.schedule(new TimerTask() {
    public void run() {
        long startTime = System.nanoTime();
        boolean updateEntitiesTableSuccess = UpdateEntitiesTable();
        if (!updateEntitiesTableSuccess){
            try {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
                logger.fatal(e.getMessage());
                System.exit(1);
            }
        } else { //everything saved to DB - commit time
            try {
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                logger.fatal(e.getMessage());
                System.exit(1);
            }
        }
        logger.debug("Time to save to DB: " + (System.nanoTime() - startTime) / 1000000 + " milliseconds");
    }
}, 0, 10000); //TODO:: figure out the perfect saving delay

我的更新方法:

private boolean UpdateEntitiesTable() {
    Iterator<Entry<String, String>> it = dbEntitiesDeletesBacklog.entrySet().iterator();
    while (it.hasNext()) {
        Entry<String, String> pairs = it.next();
        String tmpEntityId = pairs.getKey();

        int deletedSuccess = UPDATE("DELETE" + 
                " FROM " + DB_NAME + ".entities" + 
                " WHERE entity_id=(?)", new String[]{tmpEntityId});
        if (deletedSuccess != 1) {
            logger.error("Entity " + tmpEntityId + " was unable to be deleted.");
            return false;
        }
        it.remove();
        dbEntitiesDeletesBacklog.remove(tmpEntityId);
    }

在为 dbEntitiesDeletesBacklog HashMap 和本摘录中未包含的其他容器“保存到数据库”时,我是否需要创建某种锁定机制?我认为我需要这样做,因为它创建了它的迭代器,然后循环。如果在创建迭代器之后添加一些东西,并且在它完成遍历条目之前添加一些东西怎么办。很抱歉,这更像是一个流程问题,而不是代码帮助问题(因为我包含了这么多示例代码),但我想确保很容易理解我想要做什么和问什么。

我使用的其他容器也有同样的问题:

public static ArrayList<String> dbCharacterDeletesBacklog =  new ArrayList<String>();

private boolean DeleteCharactersFromDB() {
    for (String deleteWho : dbCharacterDeletesBacklog){
        int deleteSuccess = MyDBSyncher.UPDATE("DELETE FROM " + DB_NAME + ".characters" +
                " WHERE name=(?)", 
                new String[]{deleteWho});

        if (deleteSuccess != 1) {
            logger.error("Character(deleteSuccess): " + deleteSuccess);
            return false;
        }
    }
    dbCharacterDeletesBacklog.clear();
    return true;
}

非常感谢,一如既往,在这方面的任何帮助。非常感谢!

4

2 回答 2

2

至少,Collections.synchronizedMap如果您同时访问地图,则需要同步地图(通过),否则您可能会遇到不确定的行为。

除此之外,正如您所建议的,您还需要在迭代期间锁定地图。来自javadocCollections.synchronizedMap()的建议是:

当迭代其任何集合视图时,用户必须在返回的地图上手动同步:

Map m = Collections.synchronizedMap(new HashMap());
      ...
Set s = m.keySet();  // Needn't be in synchronized block
      ...
synchronized(m) {  // Synchronizing on m, not s!
    Iterator i = s.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
}

不遵循此建议可能会导致不确定的行为。

或者,使用 aConcurrentHashMap而不是常规的 HashMap 以避免在迭代期间需要同步。对于游戏来说,这可能是一个更好的选择,因为您可以避免长时间锁定您的收藏。

可能更好的是,考虑轮换新集合,这样每次更新数据库时,您都会抓取集合并将其替换为写入所有新更新的新空集合,避免在数据库写入发生时锁定集合。在这种情况下,集合将由某个容器管理,以允许这种抓取和替换是线程安全的。<<< 注意:在这种情况下,您不能将底层集合公开给修改代码,因为您需要严格保持其引用私有,以使交换生效(并且不引入任何竞争条件)。

于 2013-08-12T20:57:01.887 回答
0

这是我将使用的示例。我在这里发帖希望它能帮助其他有类似问题的人。

public class MyDBSyncher {

    public static boolean running = false;
    public static HashMap<String, String> dbEntitiesInsertsBacklog_A = new HashMap<String, String>();
    public static HashMap<String, String> dbEntitiesInsertsBacklog_B = new HashMap<String, String>();

    public MyDBSyncher(){
        Timer dbUpdateJob = new Timer();
        dbUpdateJob.schedule(new TimerTask() {
            public void run() {
                running = true;
                boolean updateEntitiesTableSuccess = UpdateEntitiesTable();
                running = false;
            }
        }, 0, 10000); //TODO:: figure out the perfect saving delay
    }

    public HashMap getInsertableEntitiesHashMap(){
        if (running){
            return dbEntitiesInsertsBacklog_B;
        } else {
            return dbEntitiesInsertsBacklog_A;
        }
    }

    private boolean UpdateEntitiesTable() {
        Iterator<Entry<String, String>> it2 = getInsertableEntitiesHashMap().entrySet().iterator();
        while (it2.hasNext()) {
            Entry<String, String> pairs = it2.next();
            String tmpEntityId = pairs.getKey();

            //some DB updates here

            it2.remove();
            getInsertableEntitiesHashMap().remove(tmpEntityId);
        }
        return true;
    }
}
于 2013-08-15T19:54:10.230 回答