Koleksiyonlar.synchronizedMap'e karşı Java senkronize blok


85

Aşağıdaki kod, çağrıları doğru şekilde senkronize etmek için ayarlanmış synchronizedMapmı?

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

Anladığım kadarıyla, addToMap()başka bir iş parçacığının çağrılmasını önlemek için remove()veya çağrıyı containsKey()geçmeden önce put()senkronize edilmiş bloğa ihtiyacım var , ancak senkronize bir bloğa ihtiyacım yok doWork()çünkü başka bir iş parçacığı addToMap()dönmeden önce senkronize edilmiş bloğa giremiyor remove()çünkü Haritayı orijinal olarak oluşturdum ile Collections.synchronizedMap(). Bu doğru mu? Bunu yapmanın daha iyi bir yolu var mı?

Yanıtlar:


90

Collections.synchronizedMap() Harita üzerinde çalıştırmak istediğiniz her atomik işlemin senkronize olacağını garanti eder.

Ancak haritada iki (veya daha fazla) işlemin çalıştırılması bir blokta senkronize edilmelidir. Yani evet - doğru şekilde senkronize ediyorsunuz.


26
Bunun işe yaradığını belirtmenin iyi olacağını düşünüyorum çünkü javadocs, senkronizedMap'in haritanın kendisinde senkronize olduğunu ve bazı dahili kilitlerin olmadığını açıkça belirtiyor. Eğer durum senkronize olsaydı (synizedMap) doğru olmazdı.
extraneon

2
@Yuval cevabınızı biraz daha derinlemesine açıklar mısınız? SychronizedMap'in işlemleri atomik olarak yaptığını söylüyorsunuz, ancak syncMap tüm işlemlerinizi atomik yaptıysa neden kendi senkronize bloğunuza ihtiyacınız olsun ki? Görünüşe göre ilk paragrafınız ikincisi için endişelenmenizi engelliyor.
almel

Benim bkz @almel cevap
Sergey

2
Harita halihazırda kullandığı için senkronize bloğa sahip olmak neden gerekli Collections.synchronizedMap()? İkinci noktayı anlamıyorum.
Bimal Sharma


13

Kodunuzda küçük bir hata potansiyeli vardır .

[ GÜNCELLEME: map.remove () kullandığı için bu açıklama tamamen geçerli değil. Bu gerçeği ilk seferde kaçırdım. :( Bunu belirttiği için sorunun yazarına teşekkürler. Gerisini olduğu gibi bırakıyorum, ancak potansiyel olarak bir hata olduğunu söylemek için kurşun ifadesini değiştirdim .]

In DoWork () Bir iş parçacığı güvenli bir şekilde Haritam'dan Liste değeri olsun. Ancak daha sonra, güvenli olmayan bir konuda bu listeye erişiyorsunuz. Örneğin, tek bir ipliğin listesini kullanarak olabilir DoWork () çağıran bir iplik ise synchronizedMap.get (anahtar) (değeri) .Add içinde addToMap () . Bu iki erişim senkronize değildir. Temel kural, bir koleksiyonun iş parçacığı güvenli garantilerinin, depoladıkları anahtarları veya değerleri kapsamamasıdır.

Bunu haritaya senkronize edilmiş bir liste ekleyerek düzeltebilirsiniz.

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

Alternatif olarak doWork () içindeki listeye erişirken harita üzerinde de senkronize edebilirsiniz :

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

Son seçenek eşzamanlılığı biraz sınırlayacak, ancak biraz daha net bir IMO.

Ayrıca ConcurrentHashMap hakkında kısa bir not. Bu gerçekten kullanışlı bir sınıftır, ancak senkronize edilmiş HashMap'ler için her zaman uygun bir alternatif değildir. Javadoc'larından alıntı yaparak,

Bu sınıf, iş parçacığı güvenliğine dayanan ancak senkronizasyon ayrıntılarına güvenmeyen programlarda Hashtable ile tamamen birlikte çalışabilir .

Başka bir deyişle, putIfAbsent () atomik ekler için harikadır ancak bu çağrı sırasında haritanın diğer bölümlerinin değişmeyeceğini garanti etmez; sadece atomikliği garanti eder. Örnek programınızda, put () s dışındaki şeyler için (senkronize edilmiş) HashMap'in senkronizasyon ayrıntılarına güveniyorsunuz.

Son şey. :) Pratikte Java Eşzamanlılığı'ndan alınan bu harika alıntı, çok iş parçacıklı programlarda hata ayıklama konusunda bana her zaman yardımcı olur.

Birden fazla iş parçacığı tarafından erişilebilen her bir değiştirilebilir durum değişkeni için, bu değişkene tüm erişimlerin tutulan aynı kilit ile gerçekleştirilmesi gerekir.


SenkronizedMap.get () ile listeye erişiyorsam, hatayla ilgili düşüncenizi görüyorum. Remove () 'i kullandığım için, bu anahtarla yapılan sonraki ekleme yeni bir ArrayList oluşturmamalı ve doWork'te kullandığım arrayla karışmamalı mı?
Ryan Ahearn

Doğru! Çıkardığını tamamen geçtim.
JLR

1
Birden fazla iş parçacığı tarafından erişilebilen her bir değiştirilebilir durum değişkeni için, bu değişkene tüm erişimlerin tutulan aynı kilit ile gerçekleştirilmesi gerekir. ---- Genelde sadece yeni bir Object () olan özel bir özellik eklerim ve bunu senkronizasyon bloklarım için kullanırım. Bu şekilde, bu bağlam için her şeyi çiğ olarak biliyorum. senkronize (objectInVar) {}
AnthonyJClink

11

Evet, doğru şekilde senkronize ediyorsunuz. Bunu daha detaylı anlatacağım. SenkronizedMap nesnesindeki iki veya daha fazla yöntem çağrısını yalnızca, synizedMap nesnesindeki yöntem çağrıları sırasındaki sonraki yöntem çağrısında önceki yöntem çağrılarının sonuçlarına güvenmeniz gerektiğinde senkronize etmeniz gerekir. Bu koda bir göz atalım:

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

Bu kodda

synchronizedMap.get(key).add(value);

ve

synchronizedMap.put(key, valuesList);

yöntem çağrıları, önceki işlemin sonucuna dayanır.

synchronizedMap.containsKey(key)

yöntem çağrısı.

Yöntem çağrılarının sırası senkronize edilmemişse, sonuç yanlış olabilir. Örneğin thread 1yöntemin uygulanması olan addToMap()ve thread 2yöntemin uygulanması olan doWork() ilgili yöntem çağrıları dizisini synchronizedMapaşağıdaki gibi bir nesne olabilir: Thread 1yöntemi yürüttü

synchronizedMap.containsKey(key)

ve sonuç " true". Bu işletim sistemi, yürütme denetimini değiştirdikten thread 2ve yürüttüğünden sonra

synchronizedMap.remove(key)

Bundan sonra, yürütme kontrolü yeniden ayarına geçirildi thread 1ve örneğin yürütüldü

synchronizedMap.get(key).add(value);

synchronizedMapnesnenin içerdiğine keyve NullPointerExceptionatılacağına inanmak çünkü synchronizedMap.get(key) geri dönecektir null. synchronizedMapNesne üzerindeki yöntem çağrılarının sırası birbirinin sonuçlarına bağlı değilse, sırayı senkronize etmenize gerek yoktur. Örneğin, bu sıralamayı senkronize etmenize gerek yoktur:

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

Buraya

synchronizedMap.put(key2, valuesList2);

yöntem çağrısı önceki sonuçlara dayanmaz

synchronizedMap.put(key1, valuesList1);

yöntem çağrısı (bazı iş parçacığının iki yöntem çağrısı arasına müdahale edip etmediği ve örneğin öğesini kaldırıp kaldırmadığı umurunda değildir key1).


4

Bu bana doğru görünüyor. Herhangi bir şeyi değiştirecek olsaydım, daha net hale getirmek için Collections.synchronizedMap () kullanmayı bırakır ve her şeyi aynı şekilde senkronize ederdim.

Ayrıca değiştirirdim

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

ile

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

3
Yapılacak şey. Collections.synchronizedXXX()Her gün uygulamamızın mantığında
kellogs

3

Senkronize etme şekliniz doğrudur. Ama bir yakalama var

  1. Koleksiyon çerçevesi tarafından sağlanan senkronize sarmalayıcı, yani ekle / al / içeren yöntem çağrılarının birbirini dışlamasını sağlar.

Ancak gerçek dünyada, değeri girmeden önce genellikle haritayı sorgularsınız. Bu nedenle iki işlem yapmanız gerekir ve bu nedenle senkronize bir blok gereklidir. Yani onu kullanma şekliniz doğrudur. Ancak.

  1. Koleksiyon çerçevesinde bulunan Eşzamanlı bir Harita uygulamasını kullanabilirdiniz. 'ConcurrentHashMap' avantajı

a. Aynı şeyleri daha verimli bir şekilde yapacak bir 'putIfAbsent' API'sine sahiptir.

b. Verimli: dThe CocurrentMap sadece anahtarları kilitler, dolayısıyla tüm haritanın dünyasını engellemez. Değerlerin yanı sıra anahtarları engellediğiniz yer.

c. Harita nesnenizin referansını kod tabanınızda başka bir yere iletmiş olabilirsiniz, burada ekibinizdeki diğer geliştiriciler onu yanlış bir şekilde kullanmaya başlayabilir. Yani, haritanın nesnesini kilitlemeden hepsi ekleyebilir () veya alabilir (). Bu nedenle onun çağrısı, senkronizasyon bloğunuza karşılıklı olarak özel olarak çalışmayacaktır. Ancak eşzamanlı bir uygulama kullanmak size hiçbir zaman yanlış kullanılamayacağı / uygulanamayacağı konusunda iç rahatlığı sağlar.


2

Check Google Tahsilat ' Multimap, ör sayfa 28 Bu sunumda .

Bu kitaplığı herhangi bir nedenle kullanamıyorsanız, ConcurrentHashMapyerine kullanmayı düşünün SynchronizedHashMap; putIfAbsent(K,V)zaten yoksa element listesini atomik olarak ekleyebileceğiniz şık bir metoda sahiptir. Ayrıca, CopyOnWriteArrayListkullanım modelleriniz bunu gerektiriyorsa, harita değerleri için kullanmayı düşünün .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.