15 Mayıs 2020 Cuma

ConcurrentHashMap computeX metodları - Atomic Metodlar

Giriş
ConcurrentHashMap sınıfı Map arayüzünden gelen bazı metodları sağlar. Bunlar
- compute()
- computeIfAbsent()
- computeIfPresent()

Normal Map kullanımında bu metodlara çok dikkat etmeyiz. Ancak ConcurrentHashMap çoklu thread ortamında kullanıldığı için bu metodların farkını ve yaptıkları şeyleri bilmek gerekir.

putIfAbsent() İle Farkı
Bu metodlar parametre olarak lambda ile kullanılır. putIfAbsent() gibi metodlar ise lambda yerine direkt nesneyi parametre olarak alırlar.

merge() ile Farkı
putIfAbsent() key yoksa, putIfPresent() ise key varsa çalışır. merge() ise key'in mevcut olup olmadığına bakmaz ve ilk değeri nesne olarak alır

1. Kısaca
compute : Atomic çalışır. key değerinin var olup olmadığın bakmaksızın lambda'yı çalıştırır. Key yoksa yeni bir value nesne ekleme, varsa da value nesnesini güncelleme imkanı sağlar.

computeIfAbsent : Atomic çalışır. Key değeri yoksa lambda'yı çalıştırır. Yeni bir value nesnesi ekleme imkanı sağlar

computeIfPresent : Atomic çalışır. Key değeri varsa lambda'yı çalıştırır. Yeni bir value nesnesi ekleme imkanı sağlar. Yeni value nesnesi null olabilir. 

Örnek - compute ve computeIfAbsent Farkı
Elimizde şöyle bir kod olsun. Bu kod yanlış. Çünkü ilklendirilen nesne thread safe olarak güncellenmiyor.
valueKeyMap.computeIfAbsent(value, val -> new HashSet<>()).add(key);
Şöyle yapmak daha gerekir.
valueKeyMap.compute(value, (k, v) -> {
    if (v == null) {
      v = new HashSet<>();
    }
    v.add(key);
    return v;
});
2. Metodlar
compute metodu - key + BiFunction
compute metodu yazısına taşıdım

computeIfAbsent metodu - key + Function
Key yoksa atomic olarak value atama içindir. İmzası şöyle.
/**
 * If the specified key is not already associated with a value,
 * attempts to compute its value using the given mapping function
 * and enters it into this map unless {@code null}.  The entire
 * method invocation is performed atomically, so the function is
 * applied at most once per key.  Some attempted update operations
 * on this map by other threads may be blocked while computation
 * is in progress, so the computation should be short and simple,
 * and must not attempt to update any other mappings of this map.
 *
 * @param key key with which the specified value is to be associated
 * @param mappingFunction the function to compute a value
 * @return the current (existing or computed) value associated with
 *         the specified key, or null if the computed value is null
 * @throws NullPointerException if the specified key or mappingFunction
 *         is null
 * @throws IllegalStateException if the computation detectably
 *         attempts a recursive update to this map that would
 *         otherwise never complete
 * @throws RuntimeException or Error if the mappingFunction does so,
 *         in which case the mapping is left unestablished
 */
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction){
    ...
}
Metodu içi şöyle.
if (map.get(key) == null) {
  V newValue = mappingFunction.apply(key);
  if (newValue != null)
    map.put(key, newValue);
}
- Direkt değer yerine lambda alır. Lambda null dönemez.
putIfAbsent() metodundan farklı olarak yeni eklenen nesneyi döner.

Örnek
Metod bir value nesnenin eklenmesine izin verir. Şöyle yaparız.
return cache.computeIfAbsent(i, (v) -> {...});
Örnek
Şöyle yaparız.
ConcurrentMap<String, String> concurrentMap = new ConcurrentHashMap<>();
String result = concurrentMap.computeIfAbsent("A", k -> "B");
System.out.println(result);  // "B"
Örnek - compute metod tercih edilmeli
Nesnenin liste olduğunu ve her thread'in bu listeye bir şey eklemeye çalıştığını varsayalım. Şöyle yaparız.
public void put(String string) {
  List<String> list = map.computeIfAbsent(string, v -> new ArrayList<>());
  synchronized(list) {
    list.add("add something");
  }
}
Örnek
Aynı hashCode değerine sahip iki farklı nesne aynı bucket'a eklenirler. Bir bucket'ı iki defa kilitlemeye kalkarsak deadlock olur. Deadlock görmek için şöyle yaparız. Burada AaAa ve BBBB aynı hashCode değerini verirler.
Map<String, Integer> map = new ConcurrentHashMap<>(16);
map.computeIfAbsent(
  "AaAa",
  key -> {
    return map.computeIfAbsent(
      "BBBB",
      key2 -> 42);
});
computeIfPresent metodu -  key + BiFunction
Key varsa atomic olarak yeni value atama içindir. Açıklaması şöyle.
If the value for the specified key is present, attempts to compute a new mapping given the key and its current mapped value. The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map.
Örnek
Yeni değer atamak için şöyle yaparız.
map.computeIfPresent(k, (key, value) -> {
    //process the value here
    //key is k
    //value is the value to which k is mapped.

    return null; //return null to remove the value after processing.
});


Hiç yorum yok:

Yorum Gönder