簡體   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