具有同步和非同步方法的ConcurrentModificationException

时间:2013-03-19 08:25:34

标签: java multithreading

所以我们有一个看起来类似的整洁的课程:

class ConnectionObserver {
    private List<DbConnection> connections;

    // singleton logic

    public synchronized void closeConnection(int id) {
        for(Iterator<DbConnection> it = connections.iterator(): it.hasNext()) {
            DbConnection conn = it.next();
            if(conn.getId() == id) {
                conn.close();
                it.remove();
            }
        }
    }

    public int countOpenConnections() {
        int open = 0;
        for(DbConnection conn : connections) {
            if(conn.isOpen()) {
                ++open;
            }
        }
        return open;
    }

    // more synchronized methods which alter the list via iterators
}

问题是,当多个线程访问单例时,某些调用会同步更改列表的方法,有些会尝试计算有时失败的打开连接,因为同时通过其中一个同步方法更改了列表。

我确信仅仅使方法countOpenConnections同步也无法解决问题。我认为,将列表设为Collections.synchronizedList也不会做太多。

你们中有些人有办法可以帮助我吗?

4 个答案:

答案 0 :(得分:1)

如果您可以进行List决赛,您可以在列表本身上进行同步 - 这样一次只有一个线程会在列表中有一个监视器。这种钝性同步以增加锁争用为代价解决了当前的问题;任何时候只有一个线程可以访问List。但是为什么多个读取线程不能同时访问列表 - 毕竟它们不会修改它...

输入ReentrantReadWriteLock,这将允许多个线程读取,但如果线程正在写入,则所有内容都必须等待。它有两种模式,“读”和“写”(因此得名)。这允许您将修改List的方法与不修改class ConnectionObserver { private List<DbConnection> connections; private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void closeConnection(int id) { final Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { for (Iterator<DbConnection> it = connections.iterator(); it.hasNext();) { DbConnection conn = it.next(); if (conn.getId() == id) { conn.close(); it.remove(); } } } finally { writeLock.unlock(); } } public int countOpenConnections() { int open = 0; final Lock readLock = readWriteLock.readLock(); readLock.lock(); try { for (DbConnection conn : connections) { if (conn.isOpen()) { ++open; } } } finally { readLock.unlock(); } return open; } // more synchronized methods which alter the list via iterators } 的方法分开 - 减少锁争用。

List

显然,更改访问synchronized以获取相应锁定的任何其他方法,并删除所有List个关键字。

另一方面,我不理解使用List - 您似乎必须在DbConnection中搜索具有特定ID的Map private Map<Integer, DbConnection> connections; public void closeConnection(int id) { final Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { final DbConnection dbConnection = connections.remove(id); if (dbConnection == null) { //handle invalid remove attempt } else { dbConnection.close(); } } finally { writeLock.unlock(); } } public int countOpenConnections() { int open = 0; final Lock readLock = readWriteLock.readLock(); readLock.lock(); try { for (final DbConnection conn : connections.values()) { if (conn.isOpen()) { ++open; } } } finally { readLock.unlock(); } return open; } 不是更好的选择(常数而不是线性时间搜索......)

{{1}}

答案 1 :(得分:0)

每次关闭连接时,都会将其从列表中删除。所以只需返回连接的大小。此外,作为一个连接观察者,您可以监听连接关闭事件并仅使用打开的连接更新列表。

答案 2 :(得分:0)

  

我确信只使countOpenConnections方法同步也无法解决问题。

实际上,这正是您正确同步程序所需的。

  

将列表设为Collections.synchronizedList也不会做太多,我想。

你是对的:synchronizedList会给你一个太精细的关键部分。

您可以使用的唯一其他方法是复制当前列表,修改副本,然后将副本分配给volatile共享变量。我不认为你会从这种方法中受益。

答案 3 :(得分:0)

首先,让countOpenConnections同步解决问题。

您可以做的另一件事是增加并发性,即使用CopyOnWriteArrayList替换connections字段的当前列表实现。这允许您在countOpenConnections上删除synchronized,从而删除争用点。但是,由于CopyOnWriteArrayList的开销,如果countConnections()的调用次数比closeConnection频繁得多,那么这才有意义。