简体   繁体   English

同步和非同步方法的ConcurrentModificationException

[英]ConcurrentModificationException with synchronized and unsynchronized methods

so we've got a neat class which looks similar to this: 所以我们有一个干净的类,看起来像这样:

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
}

The problem is, when multiple threads access the singleton some call synchronized the methods which alter the list and some try to count the open connections which fails sometimes because the list is altered in the meantime by one of the synchronized methods. 问题是,当多个线程访问单例时,一些调用会同步更改列表的方法,而有些尝试尝试计算打开的连接,有时这会失败,因为列表同时被同步方法之一更改了。

I'm convinced that just making the method countOpenConnections synchronized too won't solve the problem. 我坚信,仅使方法countOpenConnections同步也不能解决问题。 Making the list a Collections.synchronizedList won't do too much too, I think. 我认为,将列表Collections.synchronizedList不会做太多。

Do some of you have an approach which could help me? 你们中有些人有什么方法可以帮助我吗?

If you can make your List final you can synchronise on the list itself - this way only one thread will have a monitor on the list at any one time. 如果您可以将List最终List ,则可以在列表本身上进行同步-这样,在任何时候,只有一个线程在列表上具有一个监视器。 This kind of blunt synchronization solves the immediate problem at the cost of increasing lock contention; 这种钝性同步以增加锁争用为代价解决了当前的问题。 only one thread can access the List at any one time. 一次只能有一个线程可以访问List But why should multiple reading threads not be able to access the list simultaneously - they aren't modifying it after all... 但是为什么多个读取线程不能同时访问列表-毕竟它们没有修改列表...

Enter the ReentrantReadWriteLock , this would allow multiple threads to read but if a thread is writing everything would have to wait. 输入ReentrantReadWriteLock ,这将允许多个线程读取,但是如果一个线程正在写入,则所有内容都必须等待。 It had two modes, "read" and "write" (hence the name). 它有两种模式,“读”和“写”(因此得名)。 This allows you to separate methods that modify the List from those that do not - reducing lock contention. 这使您可以将修改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
}

Obviously alter any other methods that access the List to get the appropriate lock beforhand and remove any synchronized keywords. 显然,更改访问List任何其他方法以获取适当的锁定,然后删除所有synchronized关键字。

On another note, I do not understand the use of a List - it seems to you have to search the List for a DbConnection with a specific id. 另一方面,我不理解List的用法-似乎您必须在List搜索具有特定ID的DbConnection Wouldn't a Map be a better choice (constant rather than linear time search...) 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;
    }

Every time you close the connection you are removing it from the list. 每次关闭连接时,都会将其从列表中删除。 So just return the size of the connections. 因此,只需返回连接的大小即可。 Also, as its a connection observer, you may listen to connection close events and keep the list updated with only the open connections. 此外,作为连接观察者,您可以侦听连接关闭事件,并仅使用打开的连接来更新列表。

I'm convinced that just making the method countOpenConnections synchronized too won't solve the problem. 我坚信,仅使方法countOpenConnections同步也不能解决问题。

Actually that is exactly what you need to make your program correctly synchronized. 实际上,这正是使程序正确同步所需要的。

Making the list a Collections.synchronizedList won't do too much too, I think. 我认为,将列表设为Collections.synchronizedList不会做太多。

There you are right: synchronizedList will give you a too fine granularity of critical sections. 正确的地方是: synchronizedList将为您提供关键部分的精细度。

The only other approaches you have at your disposal are those that copy the current list, modify the copy, and then assign the copy to a volatile shared variable. 您可以使用的唯一其他方法是复制当前列表,修改副本,然后将副本分配给volatile共享变量的方法。 I don't think you'll benefi from such an approach. 我认为您不会从这种方法中受益。

First of all, making countOpenConnections synchronized will solve the problem. 首先,使countOpenConnections同步解决此问题。

Another thing you can do to increase concurrency, is to replace your current list implementation for the connections field with a CopyOnWriteArrayList. 您可以增加并发性的另一件事是用CopyOnWriteArrayList替换connections字段的当前列表实现。 That allows you to drop the synchronized on countOpenConnections, and therefore removes contention points. 这样一来,您就可以在countOpenConnections上删除synchronized的对象,从而删除争用点。 However, this really only makes sense if countConnections() is called much more often than closeConnection, due to the overhead of CopyOnWriteArrayList. 但是,由于CopyOnWriteArrayList的开销,只有在调用countConnections()比closeConnection频繁得多的情况下,这才真正有意义。

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

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