繁体   English   中英

同步和非同步方法的ConcurrentModificationException

[英]ConcurrentModificationException with synchronized and unsynchronized methods

所以我们有一个干净的类,看起来像这样:

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不会做太多。

你们中有些人有什么方法可以帮助我吗?

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

输入ReentrantReadWriteLock ,这将允许多个线程读取,但是如果一个线程正在写入,则所有内容都必须等待。 它有两种模式,“读”和“写”(因此得名)。 这使您可以将修改List方法与不修改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搜索具有特定ID的DbConnection 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;
    }

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

我坚信,仅使方法countOpenConnections同步也不能解决问题。

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

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

正确的地方是: synchronizedList将为您提供关键部分的精细度。

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

首先,使countOpenConnections同步解决此问题。

您可以增加并发性的另一件事是用CopyOnWriteArrayList替换connections字段的当前列表实现。 这样一来,您就可以在countOpenConnections上删除synchronized的对象,从而删除争用点。 但是,由于CopyOnWriteArrayList的开销,只有在调用countConnections()比closeConnection频繁得多的情况下,这才真正有意义。

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM