Um contêiner Java oferece um iterador à prova de falhas
Pergunta
Aqui está o meu problema:
Este pedaço de código joga um java.util.ConcurrentModificationException
, porque o Vector
listeners
é modificado enquanto existe um Iterator
Para esta estrutura de dados. O Java-Doc diz que esse contêiner oferece apenas um iterador de falha.
Existe a possibilidade de obter um Iterator
sobre um contêiner padrão como Vector
ou List
em java que me oferece um Iterator
, isso não é inválido (não é rápido), se houver um elemento removido durante isso Iterator
"vidas"?
Eu deveria ter o mesmo comportamento como o std::list
em C ++. Lá, o iterador é sempre válido, mesmo que o iterador atual seja removido. Do que o iterador está definido para o próximo elemento na lista.
public class ClientHandle {
private final Vector<ClientHandleListener> listeners = new Vector<ClientHandleListener>();
public synchronized void addListener(ClientHandleListener chl) {
listeners.add(chl);
}
public synchronized void removeListener(ClientHandleListener chl) {
listeners.remove(chl);
}
private void fireConnectionClosed() {
final ClientHandle c = this;
final Iterator<ClientHandleListener> it = listeners.iterator();
new Thread(){
@Override
public void run() {
while (it.hasNext()) {
it.next().connectionClosed(c); //FIXME the iterator gets modified
}
};
}.start();
}}
public class ClientHandlePool implements ClientHandleListener, TaskManagerListener {
/*...*/
public synchronized void removeClientHandle(ClientHandle ch) {
//here the listeners Vector from the ClientHandle gets modified
ch.removeListener(this);
ch.removeListener(currentListener);
clientHandles.remove(ch);
}
@Override
public void connectionClosed(ClientHandle ch) {
removeClientHandle(ch);
}
}
Solução
Tanto quanto eu sei, não há como retroativamente adicionar essa capacidade a qualquer padrão Collection
implementação (Iterable
na verdade).
Mas existem implementações que suportam esse tipo de comportamento, tendo respostas bem definidas à modificação simultânea enquanto itera.
Um exemplo é o CopyOnWriteList
.
Outras dicas
Em caso de ouvintes, você pode pensar em usar java.util.concurrent.CopyOnWriteArrayList
Como você normalmente tem muito mais leituras do que as gravações.
Dê uma olhada no pacote java.util.concurrent, você encontrará tudo o que precisa.
Uma maneira preguiçosa de criar um iterador rápido e seguro para falhas: pegue uma cópia da lista como uma matriz enquanto bloqueada e foreach () sobre a matriz enquanto desbloqueada ... pode ser feita com qualquer tipo de lista
private void fireConnectionClosed() {
final ClientHandle c = this;
final ClientHandleListener[] listenersArr;
synchronized(this) {
listenersArr=listeners.toArray(new ClientHandleListener[0]);
}
new Thread(){
@Override
public void run() {
for(ClientHandleListener listener : listenersArr )
listener.connectionClosed(c);
}
};
}.start();
}