8 Eylül 2019 Pazar

ConcurrentHashMap Sınıfı

Giriş
Şu satırı dahil ederiz.
import java.util.concurrent.ConcurrentHashMap;
Eğer istersek şu arayüzü kullanabiliriz.
import java.util.concurrent.ConcurrentMap;
Yani şöyle yaparız.
ConcurrentMap<String,String> cache = new ConcurrentHashMap<>();
Null Değer
Key ve value olarak null değer verilemez. Doug Lea'nin açıklaması şöyle.
The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.
Eğer mutlaka null kullanmamız gerekiyorsa value tarafında Optional kullanılabilir. Açıklaması şöyle.
A common solution is to use Optional (for pre-Java 8, use Guava's Optional) to represent the absence of a value.

So your map would have a type ConcurrentHashMap<Key, Optional<Value>>.
Lock Mekanizması
Tek bir lock ile korunan Collections.synchronizedMap yerine daha fazla lock kullanarak daha fazla ölçeklenebilirlik sağlamayı hedefliyor. Açıklaması şöyle
partitions map into segments and lock them individually instead of locking the whole map.
Bu konu hakkında yazılmış Java theory and practice: Building a better HashMap başlıklı yazı bilgilendirici.

İç Yapısı
Java 8'den itibaren bu veriyapısına bir iyileştirme yapıldı. Eğer aynı bin içine düşen fazla sayıda nesne varsa, linked liste yerine artık balanced tree (yani red black tree) ile saklanıyor.
The alternative String hash function added in 7u6 has been removed from JDK 8, along with the jdk.map.althashing.threshold system property. Instead, hash bins containing a large number of colliding keys improve performance by storing their entries in a balanced tree instead of a linked list. This JDK 8 change applies only to HashMap, LinkedHashMap, and ConcurrentHashMap.
Concurrent Kullanım Örnekleri
Örnek - Concurrent Set Olarak
Şöyle yaparız. Yani sorun olmuyor
Set<String> events = Collections.newSetFromMap(new ConcurrentHashMap<>());
events.add("1");
events.add("2");

for (String event : events) {
  events.clear();
  System.out.println(event);
}
Çıktısı şöyle
1
2
constructor
Şöyle yaparız.
ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
constructor - initialCapacity + loadFactor + concurrencyLevel
concurrencyLevel kaç tane bucket yaratılacağını belirtir. Bu sayı sonrada değiştirilemez. Bu sayı verilmez ise değeri şöyledir.
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
compute metodu - Thread Safe İlklendirme ve Güncelleme
ConcurrentHashMap computeX metodları yazısına taşıdım.

computeIfAbsent metodu - Key Yoksa Thread Safe Value İlklendirme
ConcurrentHashMap computeX metodları yazısına taşıdım.

computeIfPresent metodu - Key Varsa Thread Safe Value İlklendirme
ConcurrentHashMap computeX metodları yazısına taşıdım.

get metodu
İmzası şöyle
public V get(Object key);
Şöyle yaparız.
String key = ...
cache.get(key);
keySet metodu
İmzası şöyle.
public ConcurrentHashMap.KeySet keySet(V mappedValue);
Açıklaması şöyle.
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. The set supports element removal, which removes the corresponding mapping from this map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.

The view's iterators and spliterators are weakly consistent.
"Weakly consistent iterator" kelimesi ile şu kastediliyor. KeySetView nesnesini aldığımızda aslında bir snapshot alıyoruz. Bu seti dolaşırken ConcurrentHashMap değişebilir. Yani var olduğunu düşündüğümüz bir anahtar silinmiş olabilir.
Örnek
Şu kodda hem map değişiyor hem de keyset'i dolaşıyoruz. Beklemediğimiz bir sonuç alınabilir.
Map<Long, Long> map = ...; 
for (long key : map.keySet()) { 
  map.put(key, map.remove(key)); 
} 
Örnek
Şöyle yaparız.
for (String string : cache.keySet()) {
  System.out.println(string + "," + map.get(string));
}
Iterator ile dolaşmamız sağlar. Şöyle yaparız.
Iterator iterator = cache.keySet().iterator();

while (iterator.hasNext()) {
  System.out.println(cache.get(iterator.next()));
}
Örnek
Aynı zamanda dönen Set'e ekleme yapılaiblir. Şöyle yaparız.
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
Set<String> keySet = map.keySet("sameValue");
keySet.add("key1");
keySet.add("key2");
Bu kod şununla aynıdır
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
map.put("key1","sameValue");
map.put("key2","sameValue");
merge metodu
merge metodu yazısına taşıdım

newKeySet metodu
Concurrent set döndürür. Şöyle yaparız.
Set<Object> seen = ConcurrentHashMap.newKeySet();
put metodu
Bu veri yapısınına key ve value değerleri null atanamaz. Açıklaması şöyle
Like Hashtable but unlike HashMap, this class does not allow null to be used as a key or value.
Örnek
Şu kod exception fırlatır.
cache.put(null,"...");
putIfAbsent metodu
Açıklaması şöyle.
If the specified key is not already associated with a value, associate it with the given value. This is equivalent to
if (!map.containsKey(key))
    return map.put(key, value);
else
    return map.get(key);
except that the action is performed atomically.
- computeIfAbset() metodu gibi lambda almaz. Değeri direkt ister.
- Eğer anahtar ilk defa ekleniyorsa null döner. Eğer anahtar varsa mevcut anahtar değerini döner.
- Değer olarak null verilebilir.

Örnek
Şöyle yaparız. Anahtar ilk defa eklendiği için null döner.
ConcurrentMap<String, String> concurrentMap = new ConcurrentHashMap<>();
String result = concurrentMap.putIfAbsent("AA", "BB");
System.out.println(result);  // null
Örnek
Şöyle yaparız.
List<Integer> mappedList = cachedMap.get(mapListKey);
if (mappedList == null) {
  List<Integer> newMappedList = Lists.newArrayList();
  mappedList = cachedMap.putIfAbsent(mapListKey, newMappedList);
  if (mappedList == null) {
    mappedList = newMappedList;
  }
}
remove metodu
Açıklaması şöyle.
Removes a single instance of the specified element from this collection, if it is present (optional operation). 
Şöyle yaparız.
String key = ...
cache.remove(key);
replace metodu
Verilen anahtara ait değeri, yeni değer ile değiştirir. Eğer değiştirme başarılı ise true döner.
Örnek
Şöyle yaparız
private Integer somefunction() {
  Integer oldOrder;
  // Insert key if it isn't already present.
  oldOrder = cache.putIfAbsent(key, 1);
  if (oldOrder == null) {
    return 0;
  }
  // If we get here, oldOrder holds the previous value.
  // Atomically update it.
  while (!cache.replace(key, oldOrder, oldOrder + 1)) {
    oldOrder = count.get(key);
  }
  return oldOrder;
}

Hiç yorum yok:

Yorum Gönder