28 Kasım 2016 Pazartesi

ConcurrentModificationException Sınıfı

Giriş
Şu satırı dahil ederiz.
import java.util.ConcurrentModificationException;
ConcurrentModificationException Nedir?
İsmi sanki iki farklı thread aynı veri yapısını değiştirince fırlatılıyormuş gibi görünse de, tek thread kullanırken de fırlatılabilir. Açıklaması şöyle
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.
ConcurrentModificationException kodun doğru çalıştığı konusunda garanti vermez çünkü Iterator'ün fail fast özelliği garanti değildir.

ConcurrentModificationException Ne Zaman Atılır?
Java'da iterator ile bir listeyi dolaşırken, liste değişirse bu exception atılır. Yapılan en klasik hata diziden eleman silmek için iterator.remove() kullanmak yerine collection.remove() metodunun kullanılması. iterator.remove() metodunun açıklaması şöyle.
The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
Sebebi ise iterator'ün next() metodu listenin eleman sayısını kontrol etmesi.

1.
Yanlış kullanım şekli şöyle
for (Iterator<String> i = c.iterator(); i.hasNext();) {  
  String s = i.next();
  if(s.equals("lalala")) {
    c.remove(s);
  }
}
Doğru kullanım şekli şöyle
for (Iterator<String> i = c.iterator(); i.hasNext();) {  
  String s = i.next();
  if(s.equals("lalala")) {
    i.remove();
  }
}
while döngüsü ile şöyle yaparız.
Iterator<Job> iter = goodJobs.iterator();
while (iter.hasNext()) {
  Job j = iter.next();
  if (j.getArrivalTime() > time) {
    iter.remove();
  }
}
2. Yanlış kullanım şekli şöyle
ArrayList<Integer> answers = new ArrayList<Integer>();
...
for(Integer value : answers){
  answers.add(1+value);
  answers.add(1*value);
}
Doğru kullanım şekli şöyle
ArrayList<Integer> toAdd=new ArrayList<Integer>();
for(Integer value : answers){
  toAdd.add(1+value);
  toAdd.add(1*value);
}
answers.addAll (toAdd);
Yanılsamalar
hasNext metodu bu exception'ı atmaz. hasNext() metodu şöyledir. Sondan bir önceki elemanı silersek size 1 küçülür ve hasNext false döner.
public boolean hasNext() {
    return cursor != size;
}
Dolayısıyla aşağıdaki kod aslında hatalı olduğu halde düzgün çalışıyormuş gibi görünür. Sebebi ise B silindikten sonra, iterator 1. halen 1. indekstedir. Yani C elemanı B'nin yerine kaymıştır. Bir sonra hasNext() false döner ve döngüden çıkar. Dolayısıyla next() hiçbir zaman çağrılmadığı için exception atılmaz.
List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String string : strings)
    if ("B".equals(string))
        strings.remove("B");
System.out.println(strings);

Hiç yorum yok:

Yorum Gönder