Esse é um erro muito interessante. Um dia eu quis ver como isso acontece, fui abrindo códigos fonte e descobri.
A classe AbstractList possui um atributo privado chamado modCount ele é declarado assim:protected transient int modCount = 0;Esse atributo guarda o número de modificações desde a criação da instância. O que são modificações? São invocações a métodos públicos que alterem a estrutura interna da lista. No caso da classe ArrayList podemos citar esses exemplos:add(element);
remove(element);
trimToSize();
ensureCapacity(500);No processo de execução desses métodos, em algum momento será executada essa linha:modCount++;Agora quando fazemos assim:for(Object item : nossaLista){
// Executamos nosso código
}Estamos invocando o método iterator() da coleção, um código equivalente seria assim:Iterator<Object> ite = nossaLista.iterator();
while(ite.hasNext()){
Object item = ite.next();
// Executamos nosso código
}E quando invocamos esse método nessa classe, o modCount é enviado, e atribuido à uma variável chamada expectedModCount.
Isso é feito por segurança. Tendo esses valores, conseguimos ver se a lista foi alterada, enquanto iteramos no nosso Iterator. A cada chamada do método next() é invocado o método checkForComodification(), que por sua vez tem a seguinte implementação: final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}E voilà, achamos a exceção. Caso haja alguma alteração diretamente na lista, durante a iteração (não esqueça que usamos um iterator no foreach, mesmo sem pedir por um explicitamente), como uma invocação a remove, alteraremos o modCount, e ao buscarmos o próximo item do iterador, teremos uma diferença, fazendo com que seja lançada a exceção 
Ou seja, se for remover itens da lista, utilize um Iterator mesmo 