Eu tenho uma coleção. Muitas threads devem ser capazes de ler dela ao mesmo tempo, mas apenas uma thread deve ser capaz de escrever nela por vez, e somente quando não estiver sendo lida. O ReentrantReadWriteLock do Java parece perfeito para isso.
No entanto, estou confuso sobre como escrever o iterador para a coleção. O iterador deve obter o bloqueio de leitura quando ele começa. Mas não consigo descobrir como garantir que ele será desbloqueado no caso em que o iterador nunca terminou.
Aqui está um exemplo de código que é apenas um wrapper em torno de um iterador normal que bloqueia e desbloqueia:
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
public class ReadLockIterator<T> implements Iterator<T> {
private final Lock lock;
private final Iterator<T> iterator;
public ReadLockIterator(ReadWriteLock lock, Iterator<T> iterator) {
this.lock = lock.readLock();
this.iterator = iterator;
this.lock.lock();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
try {
return iterator.next();
}
finally {
if(!hasNext())
lock.unlock();
}
}
}
Isso funcionará bem, desde que o usuário obtenha todos os elementos do iterador. Mas o que acontece se o usuário não fizer isso? Como posso garantir que o bloqueio será eventualmente liberado, mesmo que alguns elementos nunca sejam lidos do iterador?
Meu primeiro pensamento foi colocar uma segunda verificação no finalize()
método do iterador, mas então li que finalize não deve ser usado para desbloquear.
Qual é a melhor maneira de lidar com isso?
Não há uma boa maneira de lidar com isso. A estratégia mais viável é exigir que cada usuário do seu iterador envolva o uso em lock/unlock. Se você se pegar dizendo "Essa não é uma estratégia muito viável", eu concordaria, mas isso não significa que exista uma solução melhor.
O caso das coleções sincronizadas ilustra isso, pois cada usuário dessas coleções tem que bloqueá-las manualmente: é muito estranho, mas não há maneira melhor.
Você deve criar uma classe de bloqueio para sua coleção que implemente
AutoCloseable
eIterable
.Então você pode usar try-with-resources para garantir que o bloqueio esteja fechado, e suas implementações do iterador podem falhar se o bloqueio não estiver aberto:
Pode ser que não haja uma boa solução para esse problema, como Louis Wasserman mencionou. Eu certamente não consigo pensar em uma boa.
Eu queria postar algo , mesmo que não seja uma solução muito boa. Esta é uma versão melhorada do meu wrapper de iterador. É garantido que ele desbloqueie se o iterador encapsulado atingir o fim da coleção ou se o iterador encapsulado lançar uma exceção. Ele também implementa a
AutoCloseable
interface para que ela anuncie claramente que usa um recurso que precisa ser liberado. Isso não resolve realmente o problema, mas pelo menos reduz a chance de que um usuário o use indevidamente.