简体   繁体   中英

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. Making the list a Collections.synchronizedList won't do too much too, I think.

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. 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. 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. 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.

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.

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. Wouldn't a Map be a better choice (constant rather than linear time search...)

    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.

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.

There you are right: synchronizedList will give you a too fine granularity of critical sections.

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. I don't think you'll benefi from such an approach.

First of all, making countOpenConnections synchronized will solve the problem.

Another thing you can do to increase concurrency, is to replace your current list implementation for the connections field with a CopyOnWriteArrayList. That allows you to drop the synchronized on countOpenConnections, and therefore removes contention points. However, this really only makes sense if countConnections() is called much more often than closeConnection, due to the overhead of CopyOnWriteArrayList.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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