Senkronize Blok yerine Senkronize Yöntem kullanmanın bir avantajı var mı?


401

Birisi bana bir örnek ile senkronize blok üzerinden senkronize yöntem avantajı söyleyebilir?




Yanıtlar:


431

Herkes bana senkronize yöntem avantajlı senkronize blok üzerinde bir örnek söyleyebilir? Teşekkürler.

Blok üzerinde senkronize yöntem kullanmanın açık bir avantajı yoktur.

Belki de tek (ama buna bir avantaj demezdim) nesne referansını eklemenize gerek yok this .

Yöntem:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Blok:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Görmek? Hiçbir avantajı yok.

Bloklar yapmak yöntemi senkronize tüm nesneyi kilitlemek oysa kilidi gibi başka bir nesneyi kullanabilirsiniz çünkü çoğunlukla esneklik içinde olsa yöntemlerine göre avantajları vardır.

Karşılaştırmak:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Ayrıca yöntem büyürse, senkronize edilmiş bölümü ayrı tutabilirsiniz:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
API tüketicisinin bir yararı, yöntem bildiriminde senkronize edilen anahtar kelimenin kullanılmasının, yöntemin nesne örneği üzerinde senkronize edildiğini ve (muhtemelen) iş parçacığı açısından güvenli olduğunu açıkça belirtmesidir.
Scrubbie

59
Bu eski bir soru olduğunu biliyorum ama bazı çevrelerde "bu" senkronizasyon bir anti-desen olarak kabul edilir. İstenmeyen sonuç, sınıfın dışında birisinin "buna" eşit bir nesne başvurusunu kilitleyebilmesi ve diğer iş parçacıklarının sınıf içindeki engelleri geçmesini potansiyel olarak bir kilitlenme durumu oluşturmasını engelleyebilmesidir. Bir "private final Object = new Object ();" oluşturma sadece kilitleme amacıyla değişken olan, sıklıkla kullanılan çözümdür. Doğrudan bu konuyla ilgili başka bir soru .
justin.hughey

30
"yöntemin senkronize edilmesi tüm sınıfı kilitler." Bu doğru değil. Tüm sınıfı değil, tüm örneği kilitler. Aynı sınıftan birden fazla nesne kendi kilidini tutar. :)
Selamlıyor

4
Bununla ilgili ilginç bir şey, senkronize edilmiş bir yöntem kullanmanın, üretilen bayt kodunun 1 daha az talimat vermesine neden olacağıdır, çünkü yöntemlerin imzalarına senkronize edilmiş bir bit vardır. Bayt kodunun uzunluğu, bir yöntemin sıralanıp kaplanmadığı konusunda bir faktör olduğundan, bloğu yöntem imzasına taşımak karardaki fark olabilir. Teoride zaten. Bir tasarım kararını kaydedilen tek bir bayt kod yönergesine dayandırmam, bu korkunç bir fikir gibi görünüyor. Ama yine de, bu ise bir fark. =)
corsiKa

2
@corsiKa: birden fazla talimat kaydedersiniz. Bir synchronizedblok iki talimatları kullanılarak uygulanır, monitorenterve monitorexit, artı sağlar istisna işleyicisi monitorexitbile istisnai durumlarda denir. Bir synchronizedyöntem kullanılırken tüm bunlar kaydedilir .
Holger

139

Tek gerçek fark, senkronize edilmiş bir bloğun hangi nesne üzerinde senkronize edileceğini seçebilmesidir. Senkronize yöntem yalnızca 'this'(veya senkronize sınıf yöntemi için karşılık gelen Sınıf örneği) kullanabilir. Örneğin, bunlar anlamsal olarak eşdeğerdir:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

İkincisi daha esnektir, çünkü genellikle bir üye değişken olan herhangi bir nesnenin ilişkili kilidi için rekabet edebilir . Aynı zamanda daha ayrıntılıdır, çünkü bloktan önce ve sonra eşzamanlı kod çalıştırabilirsiniz, ancak yine de yöntem içinde. Tabii ki, eşzamanlı kodu ayrı senkronize olmayan yöntemlere yeniden düzenleyerek eşitlenmiş bir yöntemi kolayca kullanabilirsiniz. Kodu daha anlaşılır yapan hangisi olursa kullanın.


Eğer foo () içindeki tüm kodların senkronize edilmesi gerekmiyorsa, ikincisinin değeri de olabilir.
Evan

1
Bu doğru, ama "Savaşçı" sordu: "Senkronize yöntemin avantajı" hiçbiri yoktur.
OscarRyz

76

Senkronize Yöntem

Artıları:

  • IDE'niz senkronize yöntemleri gösterebilir.
  • Sözdizimi daha kompakttır.
  • Senkronize blokları ayrı yöntemlere ayırmaya zorlar.

Eksileri:

  • Bunu senkronize eder ve böylece yabancıların da senkronize etmesini mümkün kılar.
  • Kodu senkronize blokun dışına taşımak daha zordur.

Senkronize blok

Artıları:

  • Kilit için özel bir değişken kullanılmasına izin verir ve böylece kilidi sınıfın içinde kalmaya zorlar.
  • Senkronize bloklar, değişkene referanslar aranarak bulunabilir.

Eksileri:

  • Sözdizimi daha karmaşıktır ve bu nedenle kodun okunması zorlaşır.

Şahsen ben sadece senkronizasyon ihtiyacı olan şeylere odaklanmış sınıflarla senkronize yöntemler kullanmayı tercih ederim. Bu sınıf mümkün olduğunca küçük olmalı ve bu nedenle senkronizasyonu gözden geçirmek kolay olmalıdır. Diğerleri senkronizasyonu önemsememelidir.


"Sınıfın içinde kal" derken " nesnenin içinde kal" mı demek istersiniz yoksa bir şey mi kaçırıyorum?
OldPeculier

36

Ana fark senkronize bir blok kullanırsanız dışında bir nesne üzerinde kilit olabilmesidir bu çok daha esnek olmasını sağlar.

Bir mesaj kuyruğunuz ve birden çok mesaj üreticisi ve tüketiciniz olduğunu varsayın. Üreticilerin birbirine müdahale etmesini istemiyoruz, ancak tüketicilerin üreticileri beklemek zorunda kalmadan mesaj alabilmesi gerekiyor. Bu yüzden sadece bir nesne yaratıyoruz

Object writeLock = new Object();

Ve bundan sonra bir üretici her yeni mesaj eklemek istediğinde bunu kilitleriz:

synchronized(writeLock){
  // do something
}

Böylece tüketiciler hala okuyabilir ve üreticiler kilitlenir.


2
Örneğiniz tahribatsız okumalarla sınırlıdır. Okuma iletiyi kuyruktan kaldırırsa, bir süre üreticinin kuyruğa yazmasıyla yapılırsa bu başarısız olur.
ceving

30

Senkronize yöntem

Senkronize yöntemlerin iki etkisi vardır.
İlk olarak, bir iş parçacığı bir nesne için eşzamanlı bir yöntem yürütürken, ilk iş parçacığı nesne ile yapılana kadar aynı nesne bloğu için eşzamanlı yöntemleri çağıran diğer tüm iş parçacıkları (yürütmeyi askıya alma).

İkincisi, senkronize edilmiş bir yöntem çıktığında, aynı nesne için senkronize edilmiş bir yöntemin daha sonraki herhangi bir çağrılmasıyla otomatik olarak gerçekleşmeden önce ilişki kurar. Bu, nesnenin durumundaki değişikliklerin tüm iş parçacıkları tarafından görülebilir olmasını sağlar.

Yapıcıların senkronize edilemeyeceğini unutmayın - bir yapıcı ile senkronize edilmiş anahtar kelimeyi kullanmak bir sözdizimi hatasıdır. Yapıcıları senkronize etmek mantıklı değildir, çünkü yalnızca bir nesneyi oluşturan iş parçacığının inşa edilirken ona erişmesi gerekir.

Senkronize Açıklama

Senkronize yöntemlerden farklı olarak, senkronize ifadeler kendinden kilit sağlayan nesneyi belirtmelidir: Çoğu zaman bunu bir liste veya haritaya erişimi senkronize etmek için kullanıyorum ancak nesnenin tüm yöntemlerine erişimi engellemek istemiyorum.

S: İçsel Kilitler ve Eşitleme Eşitleme, iç kilit veya monitör kilidi olarak bilinen bir iç varlık etrafında oluşturulur. (API spesifikasyonu genellikle bu varlığı basitçe bir "monitör" olarak ifade eder.) İçsel kilitler senkronizasyonun her iki yönünde de rol oynar: bir nesnenin durumuna özel erişim sağlamak ve görünürlük için gerekli olan ilişkiden önce ilişkiler kurmak.

Her nesnenin kendisiyle ilişkilendirilmiş kendinden kilit vardır. Kural olarak, bir nesnenin alanlarına özel ve tutarlı erişime ihtiyaç duyan bir iş parçacığının, nesneye erişmeden önce kendinden kilit alması ve onlarla iş bitince iç kilidi serbest bırakması gerekir. Bir ipliğin, kilidi edindiği ve kilidi serbest bıraktığı zaman arasında içsel kilide sahip olduğu söylenir. Bir iş parçacığının kendiliğinden bir kilidi olduğu sürece, başka hiçbir iş parçacığı aynı kilidi alamaz. Diğer iş parçacığı kilidi almaya çalıştığında engellenir.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Senkronize yöntem, blok ve senkronizasyon olmadan farklı çıkışları çapraz kontrol edin.


10
Yapıcıların senkronize edilemeyeceğini belirten tek kişi olduğu için +1 . Yani, bir kurucuda gerçekten tek bir seçeneğiniz vardır: Senkronize bloklar.
ef2011

Kodunuzu yönlendirildiği gibi test ettim ama C her zaman 0, sonra -2024260031 ve karma kodunu değiştiren tek şey. Hangi davranışlar görülmeli?
Justin Johnson

İçeriğin sağlandığı makalelerden aşağıda alıntı yapmış olmalısınız: docs.oracle.com/javase/tutorial/essential/concurrency/… ve docs.oracle.com/javase/tutorial/essential/concurrency/…
Ravindra babu

29

Not: statik senkronize yöntemler ve bloklar Class nesnesinde çalışır.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Java derleyici kaynak kodunuzu bayt koduna dönüştürdüğünde, senkronize yöntemleri ve senkronize blokları çok farklı şekilde işler.

JVM senkronize bir yöntem yürüttüğünde, yürütme iş parçacığı yöntemin method_info yapısının ACC_SYNCHRONIZED bayrağı ayarlanmış olduğunu tanımlar, sonra otomatik olarak nesnenin kilidini alır, yöntemi çağırır ve kilidi serbest bırakır. Bir istisna oluşursa, iplik otomatik olarak kilidi serbest bırakır.

Öte yandan, bir yöntem bloğunun senkronize edilmesi, JVM'nin bir nesnenin kilit ve istisna yönetimini elde etmek için yerleşik desteğini atlar ve işlevselliğin açıkça bayt kodunda yazılmasını gerektirir. Senkronize bloğu olan bir yöntemin bayt kodunu okursanız, bu işlevi yönetmek için bir düzineden fazla ek işlem görürsünüz.

Bu, hem senkronize yöntem hem de senkronize blok oluşturmak için çağrıları gösterir:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

synchronizedMethodGet()Yöntem aşağıdaki bayt kodu oluşturur:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

İşte synchronizedBlockGet()yöntemdeki bayt kodu :

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Senkronize yöntem ve blok arasındaki önemli bir fark, Senkronize blok genellikle kilit kapsamını azaltır. Kilidin kapsamı performansla ters orantılı olduğundan, kodun sadece kritik bölümünü kilitlemek her zaman daha iyidir. Senkronize blok kullanmanın en iyi örneklerinden biri, bütünü kilitlemek yerine Singleton deseninde çift kontrollü kilitlemegetInstance() yöntemi yalnızca Singleton örneği oluşturmak için kullanılan kodun kritik bölümünü kilitleriz. Kilitleme sadece bir veya iki kez gerektiğinden bu, performansı önemli ölçüde artırır.

Senkronize yöntemleri kullanırken, hem statik senkronize hem de statik olmayan senkronize yöntemleri karıştırırsanız daha dikkatli olmanız gerekir.


1
Bytecode senkronize yöntemine bakarsak bytecode daha kompakt ve basittir, neden senkronize blok daha hızlı değil?
eatSleepCode

@eatSleepCode Bu kodun JVM tarafından "derlenen" bayt kodu olduğunu unutmayın. JVM gerekli monitorenterve monitorexitkodu çalıştırmadan önce ekleyecektir .
Philip Couling

12

Çoğu zaman bunu bir liste veya haritaya erişimi senkronize etmek için kullanıyorum ancak nesnenin tüm yöntemlerine erişimi engellemek istemiyorum.

Aşağıdaki kodda, listeyi değiştiren bir iş parçacığı, haritayı değiştiren bir iş parçacığının beklemesini engellemez. Yöntemler nesne üzerinde senkronize edilmiş olsaydı, yaptıkları değişiklikler çakışmasa bile her yöntemin beklemesi gerekirdi.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

Eşzamanlı bloklarla, birden fazla eşzamanlayıcıya sahip olabilirsiniz, böylece aynı anda birden çok eşzamanlı ancak çakışmasız şey devam edebilir.


6

Senkronize yöntemler yansıma API'sini kullanarak kontrol edilebilir. Bu, modeldeki tüm yöntemler senkronize gibi bazı sözleşmeleri test etmek için yararlı olabilir .

Aşağıdaki snippet, Hashtable'ın tüm senkronize yöntemlerini yazdırır:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Senkronize bloğu kullanma hakkında önemli not: kilit nesnesi olarak ne kullandığınıza dikkat edin!

Yukarıdaki user2277816 kod parçacığı, bir dizgi hazır bilgisine başvurunun kilitleme nesnesi olarak kullanılması bakımından bu noktayı göstermektedir. Dize değişmezlerinin Java'da otomatik olarak yerleştirildiğini ve sorunu görmeye başlamanız gerektiğini unutmayın: Değişmez "kilit" üzerinde eşitlenen her kod parçası aynı kilidi paylaşır! Bu, tamamen ilgisiz kod parçalarıyla kolayca kilitlenmelere yol açabilir.

Dikkat etmeniz gereken sadece String nesneleri değildir. Kutulu temel öğeler de bir tehlikedir, çünkü otomatik boks ve valueOf yöntemleri, değere bağlı olarak aynı nesneleri yeniden kullanabilir.

Daha fazla bilgi için bkz. Https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused


5

Genellikle yöntem düzeyinde bir kilit kullanmak çok kaba olur. Neden tüm yöntemi kilitleyerek paylaşılan kaynaklara erişmeyen bir kod parçasını kilitlemelisiniz. Her nesnenin bir kilidi olduğundan, blok düzeyinde senkronizasyonu uygulamak için sahte nesneler oluşturabilirsiniz. Tüm yöntemi kilitlemediği için blok seviyesi daha verimlidir.

İşte bazı örnek

Yöntem Düzeyi

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Blok Seviyesi

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Düzenle]

For Collectiongibi Vectorve Hashtableonlar senkronize zaman vardır ArrayListya HashMapolmayan ve anahtar kelime senkronize veya Koleksiyonları eşitlenmiş yöntemin çağırmak seti gerek:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

Tek fark: senkronize bloklar, senkronize yöntemden farklı olarak granüler kilitlemeye izin verir

Temel olarak synchronizedblok veya yöntemler, bellek tutarsızlık hatalarından kaçınarak iş parçacığı güvenli kodu yazmak için kullanılmıştır.

Bu soru çok eski ve son 7 yılda çok şey değişti. İplik güvenliği için yeni programlama yapıları tanıtıldı.

synchroniedBloklar yerine gelişmiş eşzamanlılık API'sini kullanarak iş parçacığı güvenliğini sağlayabilirsiniz . Bu belge sayfası , iplik güvenliğini sağlamak için iyi programlama yapıları sağlar.

Nesneleri Kilitle , birçok eşzamanlı uygulamayı basitleştiren kilitleme deyimlerini destekler.

Yürütücüler iş parçacıklarını başlatmak ve yönetmek için üst düzey bir API tanımlar. Java.util.concurrent tarafından sağlanan yönetici uygulamaları, büyük ölçekli uygulamalar için uygun iş parçacığı havuzu yönetimi sağlar.

Eşzamanlı Koleksiyonlar büyük veri koleksiyonlarını yönetmeyi kolaylaştırır ve senkronizasyon ihtiyacını büyük ölçüde azaltabilir.

Atomik Değişkenler , senkronizasyonu en aza indiren ve bellek tutarlılığı hatalarını önlemeye yardımcı olan özelliklere sahiptir.

ThreadLocalRandom (JDK 7'de), birden fazla iş parçacığından verimli psödondom sayılarının üretilmesini sağlar.

Senkronize için daha iyi değiştirme , API kullanan ReentrantLock'turLock

Eşzamanlı karşılıklı dışlama Kilidi, eşzamanlı yöntemler ve deyimler kullanılarak erişilen örtülü monitör kilidiyle aynı temel davranış ve semantiğe sahip, ancak genişletilmiş yeteneklere sahip.

Kilitli örnek:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Diğer programlama yapıları için de java.util.concurrent ve java.util.concurrent.atomic paketlerine bakın .

Bu ilgili soruya da bakın:

Senkronizasyon ve Kilit


4

Tüm nesneleri kilitlemek için senkronize yöntem kullanılır Belirli bir nesneyi kilitlemek için senkronize blok kullanılır


3

Genelde bunlar çoğunlukla, nesnenin üstü kapalı olarak kullanılan nesnenin monitörü hakkında açık olmaktan farklıdır. Bazen göz ardı olduğunu düşünüyorum senkronize yöntemlerin bir dezavantajı size senkronize etmek için "bu" referans kullanarak aynı nesnede kilitleme dış nesnelerin kilitleme olasılığını açık bırakıyor olmasıdır. Eğer içine girerseniz bu çok ince bir hata olabilir. Dahili açık bir Nesne veya varolan başka bir alanda eşitleme yapmak, eşitlemeyi tamamen içine alan bu sorunu önleyebilir.


2

Daha önce de belirtildiği gibi, senkronize fonksiyon sadece "this" kullandığında senkronize blok kilit nesnesi olarak kullanıcı tanımlı değişkeni kullanabilir. Ve elbette, işlevinizin senkronize edilmesi gereken alanlarla manipüle edebilirsiniz. Ancak herkes kilit nesnesi olarak "this" kullanarak senkronize fonksiyon ve blok arasında hiçbir fark olmadığını söylüyor. Bu doğru değil, her iki durumda da oluşturulacak bayt kodunda fark var. Senkronize blok kullanımı durumunda, "bu" ya referans veren yerel değişken tahsis edilmelidir. Ve sonuç olarak işlev için biraz daha büyük bir boyuta sahip olacağız (sadece birkaç sayıda fonksiyonunuz varsa ilgili değil).

Burada bulabileceğiniz farkın daha ayrıntılı açıklaması: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

Senkronize yöntemler durumunda, bir Nesne üzerinde kilit alınacaktır. Ancak senkronize blokla giderseniz kilidin alınacağı bir nesneyi belirleme seçeneğiniz vardır.

Misal :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

Bunun eski bir soru olduğunu biliyorum, ama buradaki yanıtları hızlı bir şekilde okuduğumda, kimsenin zaman zaman bir synchronizedyöntemin yanlış kilit olabileceğinden bahsetmediğini gerçekten görmedim .
Uygulamada Java Eşzamanlılığı'ndan (s. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Yukarıdaki kod, iş parçacığı açısından güvenli görünmektedir . Ancak, gerçekte değil. Bu durumda, kilit sınıf örneğinde elde edilir. Ancak, listenin bu yöntemi kullanmayan başka bir iş parçacığı tarafından değiştirilmesi mümkündür . Doğru yaklaşım,

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Yukarıdaki kod , senkronize blok tamamlanıncaya kadar listeyi değiştirmeye çalışan tüm evrelerin listeyi değiştirmesini engeller.


şu anda bu kitabı okuyorum ... merak ediyorum ... eğer bu liste kamu yerine özelse ve sadece putIfAbsent yöntemi olsaydı, senkronize edilmiş (bu) yeterli olur mu? eldeki sorun liste o ListHelper dışında değiştirilebilir çünkü?
dtc

@dtc yea, liste özelse ve sınıfta başka bir yere sızdırılmamışsa, listeyi senkronize edilmiş olarak değiştiren sınıftaki diğer tüm yöntemleri işaretlediğiniz sürece bu yeterli olacaktır. Ancak, Listsenkronize edilmesi gerekmeyen bir kod günlüğü varsa , sadece yöntemi yerine tüm yöntemi kilitlemek performans sorunlarına yol açabilir
aarbor

mantıklı. cevap verdiğin için çok teşekkürler! tbh, kitabı bilgimi genişletme ve çoklu iş parçacığına nasıl yaklaşacağım konusunda oldukça yararlı buldum, ancak aynı zamanda bana tamamen yeni bir karışıklık dünyası getirdi
dtc

2

Pratik bir mesele olarak, senkronize yöntemlerin senkronize bloklara göre avantajı, daha salak dirençli olmalarıdır; kilitlemek için rasgele bir nesne seçemediğiniz için, senkronize yöntem sözdizimini yanlış bir dize değişmezine sabitlemek veya iş parçacıklarının altından değiştirilen değişken bir alanın içeriğini kilitlemek gibi aptalca şeyler yapmak için kötüye kullanamazsınız.

Öte yandan, senkronize yöntemlerle, kilidi nesneye referans alabilen herhangi bir iş parçacığı tarafından alınmasını önleyemezsiniz.

Bu nedenle, yöntemlerde bir değiştirici olarak senkronize edilmiş kullanmak, inek orkilerinizi kendilerini incitmekten korumakta daha iyidir, senkronize blokları özel son kilit nesneleriyle birlikte kullanmak, kendi kodunuzu inek orkörlerinden korumakta daha iyidir.


1

Java teknik özellikleri özetinden: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Senkronize ifade (§14.17) bir nesneye referans hesaplar; daha sonra bu nesne üzerinde bir kilit eylemi gerçekleştirmeye çalışır ve kilit eylemi başarıyla tamamlanana kadar daha fazla ilerlemez. ...

Senkronize bir yöntem (§8.4.3.5) çağrıldığında otomatik olarak bir kilit eylemi gerçekleştirir; kilit eylemi başarıyla tamamlanana kadar gövdesi yürütülmez. Yöntem bir örnek yöntemiyse , çağrıldığı örnekle ilişkili olan kilidi kilitler (yani, yöntemin gövdesinin yürütülmesi sırasında bu şekilde bilinecek nesne). Yöntem statikse , yöntemin tanımlandığı sınıfı temsil eden Class nesnesiyle ilişkili kilidi kilitler. ...

Bu açıklamalara dayanarak, önceki yanıtların çoğunun doğru olduğunu söyleyebilirim ve senkronize edilmiş bir yöntem, aksi takdirde yöntemin olduğu sınıfı temsil eden sınıf nesnesini nasıl alacağınızı anlamanız gereken statik yöntemler için özellikle yararlı olabilir. tanımladı."

Düzenleme: Aslında bu gerçek Java spec tırnak olduğunu düşündüm. Bu sayfanın spesifikasyonun sadece bir özeti / açıklaması olduğu açıklığa kavuşturuldu


1

TLDR; Ne synchronizeddeğiştiriciyi ne de synchronized(this){...}ifadeyi kullanın, ancak özel bir nesneyi tutan bir son örnek alanı synchronized(myLock){...}nerede myLock.


synchronizedYöntem bildiriminde değiştiriciyi kullanma synchronized(..){ }ile yöntem gövdesindeki ifade arasındaki fark şudur :

  • synchronizedYöntemin imzasının belirtilen değiştirici
    1. oluşturulan JavaDoc'da görünür,
    2. ile programlı belirlenebilir yansıması için bir yöntemin modifiye test ederken Modifier.SYNCHRONIZED ,
    3. kıyasla daha az yazım ve girinti gerektirir synchronized(this) { .... }ve
    4. (IDE'nize bağlı olarak) sınıf taslağında ve kod tamamlanmasında görünür,
    5. kullanan thisstatik olmayan yöntemi veya statik bir yöntem ile ilgili bildirilen parça sınıf ilan ettiği kilidi olarak nesne.
  • synchronized(...){...}İfadesi olanak sağlar
    1. yalnızca bir yöntemin gövdesinin parçalarının yürütülmesini senkronize etmek,
    2. bir kurucu içinde veya ( statik ) başlatma bloğu içinde kullanılacak,
    3. senkronize erişimi kontrol eden kilit nesnesini seçmek için.

Bununla birlikte, kullanılarak synchronizeddeğiştirici ya da synchronized(...) {...}birlikte this(olduğu gibi kilit nesne olarak synchronized(this) {...}) aynı dezavantajlara sahiptir. Her ikisi de senkronize etmek için kendi örneğini kilit nesnesi olarak kullanır. Bu tehlikelidir, çünkü yalnızca nesnenin kendisi değil , aynı zamanda bu nesneye başvuruda bulunan diğer herhangi bir harici nesne / kod da nesneyi potansiyel olarak ciddi yan etkilere (performans bozulması ve kilitlenmeler ) sahip bir senkronizasyon kilidi olarak kullanabilir .

Bu nedenle en iyi uygulama, synchronizeddeğiştiriciyi veya synchronized(...)ifadeyi thiskilit nesnesi olarak değil, bu nesneye özel bir kilit nesnesini kullanmaktır. Örneğin:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Birden fazla kilit nesnesi de kullanabilirsiniz, ancak iç içe kullanıldığında bunun kilitlenmeyle sonuçlanmadığından emin olmak için özel dikkat gösterilmelidir.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

Sanırım bu soru Thread Safe Singleton ve Double check lock ile tembel başlatma arasındaki fark hakkında . Belirli bir singleton uygulamak gerektiğinde her zaman bu makaleye başvuruyorum.

Peki, bu bir Thread Safe Singleton :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Artıları:

  1. Tembel başlatma mümkündür.

  2. İplik korumalıdır.

Eksileri:

  1. getInstance () yöntemi senkronize edilir, böylece birden çok iş parçacığı aynı anda erişemediğinden yavaş performansa neden olur.

Bu, Çift çek kilitlemeli Tembel başlatmadır :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Artıları:

  1. Tembel başlatma mümkündür.

  2. Aynı zamanda iplik korumalıdır.

  3. Senkronize edilen anahtar kelime nedeniyle performans düştü.

Eksileri:

  1. İlk kez performansı etkileyebilir.

  2. Eksileri olarak. çift ​​kontrol kilitleme yöntemi katlanabilir, bu nedenle yüksek performanslı çok iş parçacıklı uygulamalar için kullanılabilir.

Daha fazla ayrıntı için lütfen bu makaleye bakın:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/


-3

Konu ile senkronizasyon. 1) ASLA çalışmadığı bir iş parçacığında senkronize (bu) kullanmayın. (This) ile senkronize etmek, geçerli iş parçacığını kilitleme iş parçacığı nesnesi olarak kullanır. Her bir iş parçacığı diğer iş parçacıklarından bağımsız olduğundan senkronizasyon koordinasyonu YOKTUR. 2) Kod testleri, Mac'te Java 1.6'da yöntem eşitlemesinin çalışmadığını gösterir. 3) senkronize (lockObj) burada lockObj, üzerinde senkronize edilen tüm iş parçacıklarının ortak bir paylaşılan nesnesidir. 4) ReenterantLock.lock () ve .unlock () çalışır. Bunun için Java eğiticilerine bakın.

Aşağıdaki kod bu noktaları gösterir. Ayrıca, bir Vector öğesine eklenen birçok iş parçacığının herhangi bir bilgi kaybetmediğini, ArrayList ile aynı bilgilerin kaybedebileceğini göstermek için ArrayList ile değiştirilecek iş parçacığı güvenli Vector içerir. 0) Geçerli kod, yarış koşulları nedeniyle bilgi kaybını gösterir A) Geçerli A etiketli akımı yorumlayın ve üstündeki A çizgisini kaldırın, ardından çalıştırın, yöntem veri kaybeder, ancak olmamalıdır. B) A, ters B ve // ​​bitiş bloğu} adımlarını ters çevirin. Sonra sonuçları görmek için çalıştırın veri kaybı yok C) Yorum B, uncomment C Çalıştır, bkz. Tüm varyasyonları tamamlamak için zamanınız yok, umarım bu yardımcı olur. Senkronizasyon açıksa (bu) veya yöntem senkronizasyonu çalışıyorsa, lütfen test ettiğiniz Java ve OS sürümünü belirtin. Teşekkür ederim.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

1
İle senkronizasyon '(bu)' yapar çalışma ve yok değil , 'senkronizasyon nesnesi olarak geçerli iş parçacığı kullanmak' sürece geçerli nesne Konu uzanan bir sınıfın olduğunu. -1
Marne of Lorne
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.