public final class ClientGateway {
private static ClientGateway instance;
private static List<NetworkClientListener> listeners = Collections.synchronizedList(new ArrayList<NetworkClientListener>());
private static final Object listenersMutex = new Object();
protected EventHandler eventHandler;
private ClientGateway() {
eventHandler = new EventHandler();
}
public static synchronized ClientGateway getInstance() {
if (instance == null)
instance = new ClientGateway();
return instance;
}
public void addNetworkListener(NetworkClientListener listener) {
synchronized (listenersMutex) {
listeners.add(listener);
}
}
class EventHandler {
public void onLogin(final boolean isAdviceGiver) {
new Thread() {
public void run() {
synchronized (listenersMutex) {
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
}
}
}.start();
}
}
}
此代码抛出ConcurrentModificationException 但是我想如果它们在listenerMutex上同步,那么它们应该是串行执行的吗?在侦听器列表上运行的函数内的所有代码都在同步在Mutex上的同步块中运行。修改列表的唯一代码是addNetworkListener(...)和removeNetworkListener(...),但目前从不调用removeNetworkListener。
错误似乎发生的是,当onLogin函数/线程正在迭代侦听器时,仍在添加NetworkClientListener。
感谢您的见解!
编辑: NetworkClientListener是一个接口,将“onLogin”的实现留给实现该函数的编码器,但是它们对该函数的实现无法访问侦听器List。
另外,我只是完全重新检查,并且没有修改addNetworkListener()和removeNetworkListener()函数之外的列表,其他函数只迭代列表。更改代码:
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
要:
for(int i = 0; i < listeners.size(); i++)
nl.onLogin(isAdviceGiver);
似乎解决了并发问题,但我已经知道了这一点,并且想知道是什么原因造成的。
再次感谢您的继续帮助!
例外: 线程“Thread-5”中的异常java.util.ConcurrentModificationException at java.util.ArrayList $ Itr.checkForComodification(ArrayList.java:782) at java.util.ArrayList $ Itr.next(ArrayList.java:754) at chapchat.client.networkcommunication.ClientGateway $ EventHandler $ 5.run(ClientGateway.java:283)
编辑好的,我觉得有点傻。但是,谢谢你的帮助!特别是MJB&amp; jprete!
答案:某人对onLogin()的实现为网关添加了一个新的监听器。因此(因为java的同步是基于线程并且是可重入的,因此线程可能无法自行锁定)当onLogin()被调用时,我们在他的实现中,我们正在迭代监听器并在这样做的过程中,添加一个新听众。
解决方案:MJB建议使用CopyOnWriteArrayList而不是同步列表
答案 0 :(得分:3)
互斥锁只能防止来自多个线程的访问。如果nl.onLogin()
恰好具有向listeners
列表添加侦听器的逻辑,则可能抛出ConcurrentModificationException
,因为它正在被(通过迭代器)访问并且被更改(通过添加)同时进行。
编辑:更多信息可能有所帮助。我记得,Java集合通过保留每个集合的修改计数来检查并发修改。每次执行更改集合的操作时,计数都会增加。为了检查操作的完整性,在操作的开始和结束时检查计数;如果计数发生变化,则集合会在访问点处抛出ConcurrentModificationException
,而不是在修改时。对于迭代器,它会在每次调用next()
后检查计数器,因此在循环到listeners
的 next 迭代中,您应该看到异常。
答案 1 :(得分:2)
我必须承认我也没有看到它 - 如果确实没有调用removeListeners。
nl.onLogin位的逻辑是什么?如果它修改了东西,它可能会导致异常。
如果您希望监听器在添加时适度罕见,那么您可以创建列表CopyOnWriteArrayList类型 - 在这种情况下您根本不需要您的互斥锁 - CopyOnWriteArrayList完全是线程安全的,并返回弱永远不会抛出CME的一致迭代器(除了我刚刚说过的,在nl.onLogin中)。
答案 2 :(得分:1)
使用可以使用线程安全类CopyOnWriteArrayList而不是ArrayList,即使在迭代时修改它也不会抛出ConcurrentModificationException。在尝试修改(添加,更新)时进行迭代,然后它会复制列表,但是迭代器将继续处理原始列表。
它比ArrayList慢一点。在您不希望同步迭代的情况下,它非常有用。