9 Ocak 2020 Perşembe

CopyOnWriteArrayList Sınıfı - Safe Iteration İçindir

Giriş
Şu satırı dahil ederiz.
import java.util.concurrent.CopyOnWriteArrayList;
Not : CopyOnWriteArrayList bence sadece en çok safe iteration gerekiyorsa veya read işlemi write işlemine göre daha fazla ise lazım.

Açıklaması şöyle.
As name suggest CopyOnWriteArrayList creates copy of underlying ArrayList with every mutation operation e.g. add or set. Normally CopyOnWriteArrayList is very expensive because it involves costly Array copy with every write operation but its very efficient if you have a List where Iteration outnumber mutation e.g. you mostly need to iterate the ArrayList and don't modify it too often.
Sınıfın içi şöyle. Yani aslında halen bir lock var.
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
Bu lock sadece yazma işleminde kullanılır. Kod şöyledir
public boolean add(E e) {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    Object[] elements = getArray();
    int len = elements.length;
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    newElements[len] = e;
    setArray(newElements);
    return true;
  } finally {
    lock.unlock();
  }
}
Okuma işlemlerinde ise lock kullanılmaz. Write işlemi için açıklama şöyle
If you look at the underlying array reference you'll see it's marked as volatile. When a write operation occurs (such as in the above extract) this volatile reference is only updated in the final statement via setArray. Up until this point any read operations will return elements from the old copy of the array.

The important point is that the array update is an atomic operation and hence reads will always see the array in a consistent state.

The advantage of only taking out a lock for write operations is improved throughput for reads: This is because write operations for a CopyOnWriteArrayList can potentially be very slow as they involve copying the entire list.
- Yani lock kilitlendikten sonra, array'e ekleme yapılır. Böylece aynı anda iki tane write işlemi emniyetli hale gelir. 
- Read yapanlar lock kullanmadıkları için normalde bizim array'e yaptığımız güncellemeyi görmezler, ancak array volatile olduğu ve sadece atomic olarak referansı güncellendiği için read işlemi yapanlar da yeni array'i görebilir.

Diğer Notlar
CopyOnWrite aslında bir eş zamanlılık tekniği. Kendi sınıflarımızda da kullanabiliriz.
1. Sınıfımız içindeki bir Value nesnesini volatile olarak işaretleriz.
2. Value nesnesi her değiştiğinde syncronized(lock) ile kilitleriz ve yeni bir Value nesnesi yaratıp üye alana atarız.
3. Okuma için önce üye alan bir local değişkene atanır. Daha sonra okuma işlemi yapılır

ReadWrite Lock İle Karşılaştırma
Açıklaması şöyle. CopyOnWrite yönteminde her seferinden yeni bir nesne yaratıldığı için belki bu istenmeyebilir
Using CopyOnWrite, only writing threads get blocked by other writing threads. All other combinations are non-blocking. So, reading threads get never blocked and writing threads are not blocked by a reading thread.

Compare this to read-write locks where reading threads get blocked by writing threads. And where writing threads not only get blocked by other writing threads but also by reading threads.

constructor
Şöyle yaparız.
List<String> list = new CopyOnWriteArrayList<>();
constructor - Collection
Şöyle yaparız.
Collection<String> c = ...;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(c);
add metodu
Şöyle yaparız.
list.add("a");
get metodu
Metodun içi şöyle. Burada okuma için getArray()'in çağrıldığı ve yerel değişkende saklamak gibi çalıştığı görülebilir.
public E get(int index) {
  return elementAt(getArray(), index);
}

@SuppressWarnings("unchecked")
static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}
iterator metodu
- Iterator alındıktan sonra başka bir thread ekleme yapsa bile biz bunu görmeyiz ama ConcurrentModificationException da almayız.
- Iterator salt okunurdur. Iterator ile silme işlemi yapamayız. Açıklaması şöyle
The CopyOnWriteArrayList was created to allow for the possibility of safe iterating over elements even when the underlying list gets modified.

Because of the copying mechanism, the remove() operation on the returned Iterator is not permitted – resulting with UnsupportedOperationException:

Örnek
Şöyle yaparız.
List<String> l = new CopyOnWriteArrayList<>();
l.add("a");
l.add("b");
l.add("c");
Iterator<String> itr = l.iterator();
l.add("d");
while (itr.hasNext()) {
  String s = itr.next();
  System.out.println(s);
}
System.out.println(l);
Çıktı olarak şunu alırız.
a
b
c
[a, b, c, d]
set metodu

Metodun içi şöyle. Önce nesne kilitlenir. Daha sonra array kopyalanır ve tekrar atanır.
final transient Object lock = new Object();
private transient volatile Object[] array;

public E set(int index, E element) {
  synchronized (lock) {
    Object[] es = getArray();
    E oldValue = elementAt(es, index);

    if (oldValue != element) {
       es = es.clone();
      es[index] = element;
    }
    // Ensure volatile write semantics even when oldvalue == element
    setArray(es);
    return oldValue;
  }
}


Hiç yorum yok:

Yorum Gönder