[英]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.