Nesne üzerinde Java senkronize yöntem kilidi mi yoksa yöntem mi?


191

Aynı sınıfta 2 senkronize yöntemim varsa, ancak her biri farklı değişkenlere erişiyorsa, 2 iş parçacığı aynı anda bu 2 yönteme erişebilir mi? Kilit nesnede mi oluşuyor veya senkronize yöntem içindeki değişkenler kadar spesifik mi?

Misal:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

2 konu gerçekleştiren sınıf X aynı örneğini erişebilir miyim x.addA() ve x.addB()aynı zamanda?

Yanıtlar:


199

Yöntemi senkronize olarak (yazarak yaptığınız gibi public synchronized void addA()) bildirirseniz, tüm nesne üzerinde senkronize edersiniz , böylece aynı nesneden farklı bir değişkene erişen iki iş parçacığı yine de birbirini engeller.

Bir kerede yalnızca bir değişken üzerinde senkronize etmek istiyorsanız, iki değişken farklı değişkenlere erişirken birbirini engellemezse, synchronized ()bloklar halinde ayrı ayrı senkronize edersiniz . Eğer ave bnesne referansları vardı istersek:

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

Ama ilkel oldukları için bunu yapamazsınız.

Bunun yerine AtomicInteger kullanmanızı öneririm :

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
Yöntemi senkronize ederseniz, tüm nesneyi kilitlersiniz, böylece aynı nesneden farklı bir değişkene erişen iki iş parçacığı birbirini yine de engeller. Bu biraz yanıltıcı. Yöntem üzerinde senkronizasyon, işlevsel olarak yöntemin synchronized (this)gövdesi etrafında bir bloğa sahip olmakla eşdeğerdir . "This" nesnesi kilitlenmez, bunun yerine "this" nesnesi muteks olarak kullanılır ve gövdenin "this" üzerinde de senkronize edilen diğer kod bölümleriyle eşzamanlı olarak yürütülmesi engellenir. Senkronize olmayan diğer "this" alanlarını / yöntemlerini etkilemez.
Mark Peters

13
Evet, gerçekten yanıltıcı. Gerçek örnek için - Şuna bak - stackoverflow.com/questions/14447095/… - Özet: Kilitleme sadece senkronize yöntem seviyesinde ve nesnenin örnek değişkenlerine başka bir iş parçacığı tarafından erişilebilir
mac

5
İlk örnek temelde kırılmıştır. Eğer ave börneğin nesneler, idi Integerler, size olan örneklerinde senkronize edildi farklı nesneler ile değiştirilmesi uygularken ++operatörü.
Holger

cevabınızı düzeltin ve AtomicInteger'i başlatın: AtomicInteger a = new AtomicInteger (0);
Mehdi

Belki de bu anwser, nesnenin kendisinde senkronizasyon hakkında diğer bir açıklama ile güncellenmelidir: stackoverflow.com/a/10324280/1099452
lucasvc

71

Yöntem beyanında senkronize olan, bunun için sözdizimsel şekerdir:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

Statik bir yöntemde bunun için sözdizimsel şekerdir:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

Java tasarımcıları şimdi senkronizasyon hakkında ne anlaşıldığını bilselerdi, sözdizimsel şekeri eklemeyeceklerdi, çünkü eşzamanlılığın kötü uygulamalarına yol açmayacağından daha sık.


3
Doğru değil. senkronize yöntem, senkronize edilenden (nesne) farklı bir bayt kodu oluşturur. İşlevsellik eşdeğer olsa da, sözdizimsel şekerden daha fazlasıdır.
Steve Kuo

10
"Sözdizimsel şekerin" kesinlikle bayt kod eşdeğeri olarak tanımlandığını düşünmüyorum. Mesele, işlevsel olarak eşdeğer olmasıdır.
Yishai

1
Java tasarımcıları, monitörler hakkında zaten bilinenleri bilseydi, temelde Unix'in iç kısımlarını taklit etmek yerine, farklı bir şekilde yapmaları / yapmaları gerekirdi. Brinch Hansen, Java eşzamanlılık ilkellerini görünce 'açıkça boş yere çalıştım' dedi .
Lorne Marquis

Bu doğru. OP tarafından verilen örnek her yöntemi kilitliyormuş gibi görünebilir ancak aslında hepsi aynı nesneyi kilitler. Çok aldatıcı sözdizimi. Java'yı 10+ yıl kullandıktan sonra bunu bilmiyordum. Bu nedenle senkronize yöntemlerden kaçınırım. Her zaman görünmez bir nesnenin senkronize ile tanımlanan her yöntem için oluşturulduğunu düşündüm.
Peter Quiring

21

Senkronize yöntemlerle ilgili "Java ™ Eğiticileri" nden :

İlk olarak, aynı nesne üzerinde senkronize yöntemlerin iki çağrılması için serpiştirmek mümkün değildir . Bir iş parçacığı bir nesne için eşitlenmiş bir yöntem yürütürken, aynı iş parçacığı için eşitleme yöntemlerini çağıran diğer tüm iş parçacıkları, ilk iş parçacığı nesne ile yapılana kadar.

Senkronize bloklardaki "Java ™ Eğiticileri" nden :

Senkronize ifadeler, ince taneli senkronizasyon ile eşzamanlılığı iyileştirmek için de yararlıdır. Örneğin, sınıf MsLunch'ın asla birlikte kullanılmayan iki örnek alanı (c1 ve c2) olduğunu varsayalım. Bu alanların tüm güncellemeleri senkronize edilmelidir, ancak c1 güncellemesinin c2 güncellemesi ile serpiştirilmesini önlemek için hiçbir neden yoktur ve bunu yapmak gereksiz engelleme oluşturarak eşzamanlılığı azaltır. Senkronize yöntemleri kullanmak veya bununla ilişkili kilidi kullanmak yerine, yalnızca kilit sağlamak için iki nesne oluştururuz.

(Vurgu madeni)

Harmanlamayan 2 değişkeniniz olduğunu varsayalım . Böylece her birine farklı bir iş parçacığından aynı anda erişmek istersiniz. Kilidi , nesne sınıfının kendisinde değil, aşağıdaki gibi Object sınıfında tanımlamanız gerekir (ikinci Oracle bağlantısından örnek):

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

14

Erişilen kilit, yöntemde değil nesnenin üzerindedir. Yöntem içinde hangi değişkenlere erişildiği önemsizdir.

Yönteme "senkronize" eklenmesi, kodu çalıştıran iş parçacığının devam etmeden önce nesne üzerindeki kilidi alması gerektiği anlamına gelir. "Statik senkronize" eklemek, kodu çalıştıran iş parçacığının devam etmeden önce sınıf nesnesindeki kilidi alması gerektiği anlamına gelir. Alternatif olarak, kodu aşağıdaki gibi bir blokta sarabilirsiniz:

public void addA() {
    synchronized(this) {
        a++;
    }
}

böylece kilidinin alınması gereken nesneyi belirtebilirsiniz.

İçeren nesneyi kilitlemekten kaçınmak istiyorsanız, aşağıdakiler arasından seçim yapabilirsiniz:


7

Oracle Belgelendirme Bağlantısından

Yöntemleri senkronize etmenin iki etkisi vardır:

İlk olarak, aynı nesne üzerinde senkronize yöntemlerin iki çağrılması için serpiştirmek mümkün değildir. Bir iş parçacığı bir nesne için eşitlenmiş bir yöntem yürütürken, aynı iş parçacığı için eşitleme yöntemlerini çağıran diğer tüm iş parçacıkları, ilk iş parçacığı nesne ile yapılana kadar.

İ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 bir ilişki kurar. Bu, nesnenin durumundaki değişikliklerin tüm iş parçacıkları tarafından görülebilir olmasını sağlar

İçsel kilitleri ve kilit davranışını anlamak için bu dokümantasyon sayfasına bakınız .

Bu, sorunuza cevap verecektir: Aynı x nesnesinde, senkronize yöntemlerin yürütülmesi devam ederken x.addA () ve x.addB () öğelerini aynı anda çağıramazsınız.


4

Senkronize edilmeyen ve örnek değişkenlerine erişip değiştiren bazı yöntemleriniz varsa. Örneğinizde:

 private int a;
 private int b;

diğer nesneler aynı nesnenin senkronize yöntemindeyken ve örnek değişkenlerinde değişiklik yapabildiğinde, herhangi bir sayıda evre bu senkronize olmayan yöntemlere aynı anda erişebilir. Örneğin: -

 public void changeState() {
      a++;
      b++;
    }

Senkronize olmayan yöntemlerin örnek değişkenlerine eriştiği ve değiştirdiği senaryosundan kaçınmanız gerekir, aksi takdirde senkronize edilmiş yöntemleri kullanmanın bir anlamı yoktur.

Aşağıdaki senaryoda: -

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

İş parçacıklarından yalnızca biri addA veya addB yönteminde olabilir, ancak aynı zamanda herhangi bir iş parçacığı changeState yöntemine girebilir. İki iş parçacığı aynı anda addA ve addB giremez (Nesne düzeyi kilitleme nedeniyle), ancak aynı zamanda herhangi bir iş parçacığı changeState girebilir.


3

Aşağıdaki gibi bir şey yapabilirsiniz. Bu durumda, senkronize etmek için "this" üzerindeki kilit yerine a ve b üzerindeki kilidi kullanırsınız. İnt kullanamayız çünkü ilkel değerlerin kilitleri yoktur, bu nedenle Integer kullanırız.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

Evet, senkronize yöntem WHOLE sınıf nesnesine sivri uçlu olarak uygulandığı için diğer yöntemi engelleyecektir ... ancak yine de, addA veya addB girdiği yöntemdeki toplamı gerçekleştirirken YALNIZCA diğer iş parçacığının yürütülmesini engelleyecektir , çünkü bittiği zaman ... bir iş parçacığı nesneyi serbest bırakır ve diğer iş parçacığı diğer yönteme erişir ve bu şekilde mükemmel çalışır.

"Senkronize" tam olarak belirli bir kod yürütme sırasında diğer iş parçacığı başka bir erişim engellemek için yapılır demek. NİÇİN SONRA BU KOD GÜZEL OLACAKTIR.

Son bir not olarak, sadece bir benzersiz değişken 'a' ya da başka bir ad değil, bir 'a' ve 'b' değişkenleri varsa, diğer varyantlara mükemmel bir şekilde erişebilmesinden dolayı bu yöntemleri senkronize etmeye gerek yoktur (Diğer bellek yer).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

De çalışacak


2

Bu örnek (hoş olmasa da) kilitleme mekanizması hakkında daha fazla bilgi sağlayabilir. Eğer incrementA edilir senkronize ve incrementB edilir senkronize değil , daha sonra incrementB kısa sürede yürütülecektir, ancak eğer incrementB de olduğu senkronize o zaman için 'bekle' zorundadır incrementA önce bitirmek için incrementB işini yapabilir.

Her iki yöntem de tek örnek nesneye çağrılır, bu örnekte : ve 'rakip' iş parçacıkları aThread ve main şeklindedir .

IncrementB içinde ve bu olmadan ' senkronize ' ile deneyin ve farklı sonuçlar göreceksiniz. IncrementB ' senkronize ' ise, incrementA () 'nın bitmesini beklemesi gerekir. Her değişkeni birkaç kez çalıştırın.

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}

1

Java senkronizasyonunda, bir evre eşzamanlama yöntemine girmek isterse, sadece o iş parçacığının kullandığı tek bir eşzamanlı yöntemde değil, o nesnenin tüm eşzamanlı yöntemlerinde kilit elde eder. Bu nedenle, addA () yürüten bir iş parçacığı, her ikisi de senkronize olduğu için addA () ve addB () üzerinde kilit elde eder.Bu yüzden aynı nesneye sahip diğer iş parçacıkları addB () çalıştıramaz.


0

Tamsayıdan int ve viceversa'ya boks ve otomatik boks JVM'ye bağlı olduğundan ve -128 ile 127 arasındaysa iki farklı sayının aynı adrese hash edilebileceği olasılığı yüksek olmayabilir.

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.