[英]ConcurrentModicationException due to unsynchronized synchronized methods
[英]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.