É simples.
Considere o risco de um iterator que não faz isso. Você itera até a penultima posição. Então, paralelamente, alguém (que pode ser seu próprio método de exclusão) vai lá e exclui 2 registros. Logo depois, você tenta avançar uma posição. Lembre-se: seu iterator não sabe que os registros foram excluídos, nem quais foram. O que acontece? Você provavalmente cairia num IndexOutOfBounds. Fora isso, o iterator poderia, sem saber, ter uma posição “pulada” e acabar não fazendo o comportamento esperado. Por exemplo, você está excluindo todos os 3 “João” da sua lista ordenada. Após excluir o primeiro, algum outro trecho do programa exclui uma “Ana”. O iterator do João, acaba ficando posicionado no último João (já que a Ana vem antes do João, o que fez todos os elementos posteriores voltarem uma posição), pulando o João do meio, e seu método simplesmente não realiza aquela exclusão. Inconsistente, não?
Por isso, o iterator faz uma verificação e não navegará mais se exclusões forem feitas paralelamente.
A maneira “segura” de se excluir enquanto se percorre uma lista é através do Iterator. Assim, ele fica sabendo não só que houve uma exclusão, mas também quem foi excluído e se é ou não possível continuar iterando.
Em alguns métodos, onde você só precisa de informação filtrada, as vezes é mais fácil fazer uma cópia da lista. Aí você itera pela lista original, e exclui objetos da cópia. A performance disso não é maravilhosa, mas é extremamente simples de se implementar, gera um código limpo e bastante válido para listas pequenas.
PS: Existem listas que tem Iterators mais espertos no pacote java.util.concurrent. Se você realmente precisar de uma lista com acessos em paralelo, use as listas desse pacote.