Java'da farklı tipte güvenli İş Parçacığı Setleri


135

Java'da iş parçacığı için güvenli Setler oluşturmanın birçok farklı uygulaması ve yolu var gibi görünüyor. Bazı örnekler

1) CopyOnWriteArraySet

2) Collections.synchronizedSet (Set grubu)

3) ConcurrentSkipListSet

4) Collections.newSetFromMap (yeni ConcurrentHashMap ())

5) (4) 'e benzer şekilde üretilen diğer setler

Bu örnekler Eşzamanlılık Deseni'nden gelir: Java 6'daki Eşzamanlı Küme uygulamaları

Birisi lütfen bu örneklerin ve diğerlerinin farklılıklarını, avantajlarını ve dezavantajlarını açıklayabilir mi? Java Std Docs her şeyi anlamak ve düz tutmakta sorun yaşıyorum.

Yanıtlar:


206

1) CopyOnWriteArraySetOldukça basit bir uygulamadır - temel olarak bir dizideki öğelerin bir listesine sahiptir ve listeyi değiştirirken diziyi kopyalar. Şu anda çalışan yinelemeler ve diğer erişim işlemleri eski diziyle devam eder, bu da okuyucular ve yazarlar arasında eşitleme zorunluluğunu ortadan kaldırır (ancak yazmanın kendisinin senkronize edilmesi gerekir). contains()Diziler doğrusal zamanda aranacağından, normalde hızlı ayarlanan işlemler (özellikle ) burada oldukça yavaştır.

Bunu sadece sık sık okunacak (yinelenecek) ve nadiren değiştirilecek gerçekten küçük setler için kullanın. (Swing dinleyici setleri bir örnek olabilir, ancak bunlar gerçekten set değildir ve yine de sadece EDT'den kullanılmalıdır.)

2) Collections.synchronizedSetsadece senkronize bir bloğu orijinal kümenin her yönteminin etrafına sarar. Orijinal sete doğrudan erişmemelisiniz. Bu, kümenin iki yönteminin aynı anda yürütülemeyeceği anlamına gelir (biri diğeri bitene kadar engellenir) - bu iş parçacığı için güvenlidir, ancak birden çok iş parçacığı gerçekten seti kullanıyorsa eşzamanlılığınız olmayacaktır. Yineleyiciyi kullanırsanız, yineleyici aramaları arasında kümeyi değiştirirken ConcurrentModificationExceptions öğesinden kaçınmak için genellikle harici olarak eşitlemeniz gerekir. Performans, orijinal setin performansı gibi olacaktır (ancak bazı senkronizasyon yükleri ve eşzamanlı olarak kullanılırsa engelleme ile).

Yalnızca düşük eşzamanlılığınız varsa ve tüm değişikliklerin diğer iş parçacıkları tarafından hemen görülebildiğinden emin olmak istiyorsanız bunu kullanın.

3) en temel işlemler O (log n) ile ConcurrentSkipListSeteşzamanlı bir SortedSetuygulamadır. Yinelemenin, yineleyici oluşturulduğundan bu yana değişiklikler hakkında bilgi verebileceği veya vermeyebileceği eşzamanlı ekleme / çıkarma ve okuma / yineleme sağlar. Toplu işlemler, atomik olarak değil, birden çok tekli çağrıdır - diğer iş parçacıkları sadece bazılarını gözlemleyebilir.

Açıkçası bunu sadece öğelerinizde toplam siparişiniz varsa kullanabilirsiniz. Bu, yüksek eşzamanlı olmayan durumlar için, çok büyük olmayan kümeler için (O (log n) nedeniyle) ideal bir aday gibi görünüyor.

4) ConcurrentHashMap(ve bundan türetilen Set) için: Burada hashCode(), HashMap / için olduğu gibi, en temel seçenekler (ortalama olarak, iyi ve hızlı bir şekilde sahipseniz ) (ancak O (n) 'ye dejenere olabilir) HashSet. Yazma için sınırlı bir eşzamanlılık vardır (tablo bölümlendirilmiştir ve yazma erişimi gerekli bölümde senkronize edilecektir), okuma erişimi ise kendisiyle ve yazma iş parçacıklarıyla tamamen eşzamanlıdır (ancak şu anda yapılan değişikliklerin sonuçlarını henüz göremeyebilir) yazılı). Yineleyici, oluşturulduğundan beri değişiklikleri görebilir veya görmeyebilir ve toplu işlemler atomik değildir. Yeniden boyutlandırma yavaştır (HashMap / HashSet için olduğu gibi), bu nedenle oluşturmada gerekli boyutu tahmin ederek (ve 3/4 dolu olduğunda yeniden boyutlandırıldığı için bunun yaklaşık 1 / 3'ünü kullanarak) bundan kaçınmaya çalışın.

Büyük setleriniz, iyi (ve hızlı) bir karma fonksiyonunuz varsa ve haritayı oluşturmadan önce set boyutunu ve gerekli eşzamanlılığı tahmin edebildiğinizde bunu kullanın.

5) Burada kullanılabilecek başka eşzamanlı harita uygulamaları var mı?


1
Sadece 1) 'de bir görüş düzeltmesi, verileri yeni diziye kopyalama işlemi senkronize edilerek kilitlenmelidir. Bu nedenle, CopyOnWriteArraySet eşitleme zorunluluğunu tamamen ortadan kaldırmaz.
CaptainHastings

On ConcurrentHashMaptabanlı seti, "böylece oluşturulması üzerinde gerekli boyutunu tahmin ederek bu kaçınmaya çalışın." Haritaya verdiğiniz boyut tahmininizin (veya bilinen değerin)% 33'ünden büyük olmalıdır, çünkü set% 75 yükte yeniden boyutlandırılır. Ben kullanımexpectedSize + 4 / 3 + 1
Daren

@Daren Sanırım ilk bir +olması gerekiyor *?
Paŭlo Ebermann

@ PaŭloEbermann Tabii ki ... olmalıexpectedSize * 4 / 3 + 1
Daren

1
Java 8 için ConcurrentMap(veya HashMap) aynı grupla eşlenen girişlerin sayısı eşik değerine ulaşırsa (16 olduğuna inanıyorum) liste ikili bir arama ağacına (önceden belirlenecek kırmızı-siyah ağaç) değiştirilir ve bu durumda arama zaman olur O(lg n)ve olmazdı O(n).
akhil_mittal

20

Her modifikasyondaki tüm seti kullanarak ve değiştirerek contains()performansını HashSeteşzamanlılık ile ilgili özelliklerle birleştirmek mümkündür .CopyOnWriteArraySetAtomicReference<Set>

Uygulama taslağı:

public abstract class CopyOnWriteSet<E> implements Set<E> {

    private final AtomicReference<Set<E>> ref;

    protected CopyOnWriteSet( Collection<? extends E> c ) {
        ref = new AtomicReference<Set<E>>( new HashSet<E>( c ) );
    }

    @Override
    public boolean contains( Object o ) {
        return ref.get().contains( o );
    }

    @Override
    public boolean add( E e ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( current.contains( e ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.add( e );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

    @Override
    public boolean remove( Object o ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( !current.contains( o ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.remove( o );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

}

Aslında AtomicReferencedeğeri değişken olarak işaretler. Bu, hiçbir iş parçacığının eski verileri okumadığından emin olmadığı ve happens-beforekod derleyici tarafından yeniden sıralanamayacağı için garanti sağladığı anlamına gelir . Ancak sadece get / set yöntemleri AtomicReferencekullanılırsa, değişken değişkenimizi fantezi bir şekilde işaretliyoruz.
akhil_mittal

Bu cevap yeterince yükseltilemez çünkü (1) bir şeyi kaçırmadıkça, tüm koleksiyon türleri için çalışacaktır (2) diğer sınıfların hiçbiri tüm koleksiyonu aynı anda atomik olarak güncellemek için bir yol sağlamaz ... Bu çok yararlı .
Gili

Bu kelimeyi uygun şekilde denemeye çalıştım, ancak abstractgörünüşe göre birkaç yöntem yazmak zorunda kalmamak için etiketlendi . Onları eklemeye başladım, ancak birlikte bir birlikte gösterime girdim iterator(). Modeli kırmadan bu şey üzerinde bir yineleyiciyi nasıl koruyacağımı bilmiyorum. Her zaman geçmem gerekiyor refve her seferinde farklı bir alt küme alabilir, bu da alt kümede yeni bir yineleyici almayı gerektirir, ki bu sıfır öğe ile başlayacağından benim için işe yaramaz. Herhangi bir görüşün var mı?
nclark

Tamam, sanırım garanti, her müşterinin zamanında sabit bir anlık görüntü almasıdır, bu yüzden temel koleksiyonun yineleyicisi ihtiyacınız olan her şey olursa iyi çalışır. Benim kullanım durumum, rakip iş parçacıklarının içinde tek tek kaynakları "talep etmesine" izin vermektir ve kümenin farklı sürümleri varsa çalışmaz. İkinci olsa ... Sanırım benim iş parçacığı sadece yeni bir yineleyici almak ve CopyOnWriteSet.remove (selected_item) yanlış döndürürse tekrar denemek gerekiyor ... Ne olursa olsun yapmak gerekir :)
nclark

11

Javadoc'lar yardımcı olmazsa, muhtemelen veri yapıları hakkında okumak için bir kitap veya makale bulmalısınız. Bir bakışta:

  • CopyOnWriteArraySet, koleksiyonu her değiştirdiğinizde temel dizinin yeni bir kopyasını oluşturur, böylece yazma işlemleri yavaştır ve yineleyiciler hızlı ve tutarlıdır.
  • Collections.synchronizedSet (), bir Set threadsafe yapmak için eski okul senkronize yöntem çağrılarını kullanır. Bu düşük performanslı bir sürüm olacaktır.
  • ConcurrentSkipListSet tutarsız toplu işlemler (addAll, removeAll, vb.) Ve Yineleyiciler ile performans yazmaları sunar.
  • Collections.newSetFromMap (yeni ConcurrentHashMap ()), okuma veya yazma için mutlaka optimize edilmediğine inandığım, ancak ConcurrentSkipListSet gibi tutarsız toplu işlemlere sahip olduğuna inandığım ConcurrentHashMap'in anlambilimine sahiptir.


1

Eşzamanlı zayıf referans seti

Başka bir bükülme, iplik açısından güvenli bir dizi zayıf referanstır .

Böyle bir küme, pub-sub senaryosundaki aboneleri izlemek için kullanışlıdır . Bir abone başka yerlerde kapsam dışına çıktığında ve bu nedenle çöp toplama adayı olmaya yöneldiğinde, abonenin nazikçe abonelikten vazgeçmesi gerekmez. Zayıf referans, abonenin çöp toplama adayı olmaya geçişini tamamlamasına izin verir. Çöp sonunda toplandığında, kümedeki giriş kaldırılır.

Birlikte verilen sınıflarla doğrudan böyle bir küme sağlanmamış olsa da, birkaç çağrı ile bir küme oluşturabilirsiniz.

İlk Setolarak WeakHashMapsınıftan yararlanarak zayıf referanslar yapmaya başlarız . Bu, için sınıf belgelerinde gösterilmiştir Collections.newSetFromMap.

Set< YourClassGoesHere > weakHashSet = 
    Collections
    .newSetFromMap(
        new WeakHashMap< YourClassGoesHere , Boolean >()
    )
;

Değer haritanın, Booleanolarak, burada alakasız Anahtar haritanın yukarı bizim yapar Set.

Pub-sub gibi bir senaryoda, aboneler ve yayıncılar ayrı iş parçacıkları üzerinde çalışıyorsa (büyük olasılıkla durum) iş parçacığı güvenliğine ihtiyacımız var.

Bu seti iş parçacığı için güvenli hale getirmek üzere senkronize bir set olarak sararak bir adım daha gidin. Çağrısına yönlendirin Collections.synchronizedSet.

this.subscribers =
        Collections.synchronizedSet(
                Collections.newSetFromMap(
                        new WeakHashMap <>()  // Parameterized types `< YourClassGoesHere , Boolean >` are inferred, no need to specify.
                )
        );

Artık sonuçlarımızdan abone ekleyip kaldırabiliriz Set. Çöp toplama işlemi tamamlandıktan sonra "kaybolan" tüm aboneler otomatik olarak kaldırılacaktır. Bu yürütme gerçekleştiğinde, JVM'nizin çöp toplayıcı uygulamasına ve şu anda çalışma zamanı durumuna bağlıdır. Altta yatan WeakHashMapgirişlerin ne zaman ve nasıl silineceği ile ilgili tartışma ve örnek için bu soruya bakın, * WeakHashMap sürekli büyüyor mu veya çöp anahtarlarını temizliyor mu? * .

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.