Metode Java disinkronkan mengunci pada objek, atau metode?

191

Jika saya memiliki 2 metode yang disinkronkan di kelas yang sama, tetapi masing-masing mengakses variabel yang berbeda, dapatkah 2 utas mengakses 2 metode tersebut pada waktu yang sama? Apakah kunci terjadi pada objek, atau apakah itu spesifik seperti variabel di dalam metode yang disinkronkan?

Contoh:

class X {

    private int a;
    private int b;

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

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

}

Bisakah 2 utas mengakses instance yang sama dari pertunjukan kelas X x.addA() dan x.addB()pada saat yang sama?

Wuntee
sumber

Jawaban:

199

Jika Anda mendeklarasikan metode sebagai tersinkronisasi (seperti yang Anda lakukan dengan mengetik public synchronized void addA()), Anda menyinkronkan seluruh objek, jadi dua utas yang mengakses variabel berbeda dari objek yang sama ini akan tetap saling memblokir.

Jika Anda ingin menyinkronkan hanya pada satu variabel pada satu waktu, sehingga dua utas tidak akan memblokir satu sama lain saat mengakses variabel yang berbeda, Anda harus menyinkronkannya secara terpisah dalam synchronized ()blok. Jika adan badalah referensi objek yang akan Anda gunakan:

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

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

Tetapi karena mereka primitif, Anda tidak dapat melakukan ini.

Saya menyarankan Anda untuk menggunakan AtomicInteger sebagai gantinya:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

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

    public void addB(){ 
        b.incrementAndGet();
    }
}
OscarRyz
sumber
181
Jika Anda menyinkronkan pada metode yang Anda mengunci seluruh objek, maka dua utas yang mengakses variabel yang berbeda dari objek yang sama ini akan tetap saling memblokir. Itu agak menyesatkan. Sinkronisasi pada metode secara fungsional setara dengan memiliki synchronized (this)blok di sekitar tubuh metode. Objek "ini" tidak menjadi terkunci, melainkan objek "ini" digunakan sebagai mutex dan tubuh dicegah dari mengeksekusi bersamaan dengan bagian kode lain yang juga disinkronkan pada "ini." Ini tidak berpengaruh pada bidang / metode lain dari "ini" yang tidak disinkronkan.
Mark Peters
13
Ya, itu benar-benar menyesatkan. Sebagai contoh nyata - Lihat ini - stackoverflow.com/questions/14447095/... - Rangkuman: Mengunci hanya pada level metode yang disinkronkan dan variabel instance objek dapat diakses oleh utas lainnya
mac
5
Contoh pertama rusak secara mendasar. Jika adan bobjek, misalnya Integers, Anda menyinkronkan pada instance yang Anda ganti dengan objek yang berbeda saat menerapkan ++operator.
Holger
perbaiki jawaban Anda dan inisialisasi AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi
Mungkin anwser ini harus diperbarui dengan yang dijelaskan dalam yang lain ini tentang sinkronisasi pada objek itu sendiri: stackoverflow.com/a/10324280/1099452
lucasvc
71

Disinkronkan pada deklarasi metode adalah gula sintaksis untuk ini:

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

Pada metode statis itu adalah gula sintaksis untuk ini:

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

Saya pikir jika desainer Java tahu kemudian apa yang dipahami sekarang tentang sinkronisasi, mereka tidak akan menambahkan gula sintaksis, karena lebih sering mengarah pada implementasi konkurensi yang buruk.

Yishai
sumber
3
Tidak benar. metode yang disinkronkan menghasilkan bytecode yang berbeda dari yang disinkronkan (objek). Sementara fungsionalitasnya setara, itu lebih dari sekadar gula sintaksis.
Steve Kuo
10
Saya tidak berpikir bahwa "gula sintaksis" secara ketat didefinisikan sebagai byte-code equivalent. Intinya adalah secara fungsional setara.
Yishai
1
Jika para desainer Java tahu apa yang sudah diketahui tentang monitor mereka akan / seharusnya melakukannya secara berbeda, daripada pada dasarnya meniru jeroan Unix. Per Brinch Hansen berkata 'jelas saya telah bekerja dengan sia-sia' ketika dia melihat konklusi Jawa primitif .
Marquis of Lorne
Ini benar. Contoh yang diberikan oleh OP akan muncul untuk mengunci setiap metode tetapi pada kenyataannya mereka semua mengunci pada objek yang sama. Sintaks yang sangat menipu. Setelah menggunakan Java selama 10+ tahun saya tidak tahu ini. Jadi saya akan menghindari metode yang disinkronkan untuk alasan ini. Saya selalu berpikir bahwa objek tak terlihat dibuat untuk setiap metode yang didefinisikan dengan disinkronkan.
Peter Quiring
21

Dari "Tutorial Java ™" pada metode yang disinkronkan :

Pertama, tidak mungkin untuk dua doa metode disinkronkan pada objek yang sama untuk interleave. Ketika satu utas menjalankan metode yang disinkronkan untuk suatu objek, semua utas lainnya yang menjalankan metode yang disinkronkan untuk blok objek yang sama (menunda eksekusi) hingga utas pertama selesai dengan objek tersebut.

Dari "Tutorial Java ™" pada blok yang disinkronkan :

Pernyataan yang disinkronkan juga berguna untuk meningkatkan konkurensi dengan sinkronisasi berbutir halus. Misalkan, misalnya, kelas MsLunch memiliki dua bidang contoh, c1 dan c2, yang tidak pernah digunakan bersama. Semua pembaruan bidang ini harus disinkronkan, tetapi tidak ada alasan untuk mencegah pembaruan c1 dari disisipkan dengan pembaruan c2 - dan dengan demikian mengurangi konkurensi dengan membuat pemblokiran yang tidak perlu. Alih-alih menggunakan metode yang disinkronkan atau menggunakan kunci yang terkait dengan ini, kami membuat dua objek semata-mata untuk memberikan kunci.

(Penekanan milikku)

Misalkan Anda memiliki 2 variabel non-interleaving . Jadi, Anda ingin mengakses masing-masing dari utas yang berbeda secara bersamaan. Anda perlu menentukan kunci tidak pada kelas objek itu sendiri, tetapi pada kelas Object seperti di bawah ini (contoh dari link Oracle kedua):

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++;
        }
    }
}
MehdiMAH
sumber
14

Kunci yang diakses ada pada objek, bukan pada metode. Variabel mana yang diakses dalam metode ini tidak relevan.

Menambahkan "disinkronkan" ke metode berarti utas yang menjalankan kode harus mendapatkan kunci pada objek sebelum melanjutkan. Menambahkan "sinkronisasi statis" berarti utas yang menjalankan kode harus memperoleh kunci pada objek kelas sebelum melanjutkan. Atau Anda dapat membungkus kode dalam blok seperti ini:

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

sehingga Anda dapat menentukan objek yang kuncinya harus diperoleh.

Jika Anda ingin menghindari mengunci pada objek yang mengandung Anda dapat memilih antara:

Nathan Hughes
sumber
7

Dari tautan dokumentasi oracle

Membuat metode yang disinkronkan memiliki dua efek:

Pertama, tidak mungkin untuk dua doa metode disinkronkan pada objek yang sama untuk interleave. Ketika satu utas menjalankan metode yang disinkronkan untuk suatu objek, semua utas lainnya yang menjalankan metode yang disinkronkan untuk blok objek yang sama (menunda eksekusi) hingga utas pertama selesai dengan objek tersebut.

Kedua, ketika metode yang disinkronkan keluar, itu secara otomatis membangun hubungan yang terjadi sebelum dengan doa berikutnya dari metode yang disinkronkan untuk objek yang sama. Ini menjamin bahwa perubahan keadaan objek terlihat oleh semua utas

Lihat halaman dokumentasi ini untuk memahami kunci intrinsik dan perilaku kunci.

Ini akan menjawab pertanyaan Anda: Pada objek yang sama x, Anda tidak dapat memanggil x.addA () dan x.addB () pada saat yang sama ketika salah satu eksekusi metode yang disinkronkan sedang berlangsung.

Aditya W
sumber
4

Jika Anda memiliki beberapa metode yang tidak disinkronkan dan sedang mengakses dan mengubah variabel instan. Dalam contoh Anda:

 private int a;
 private int b;

sejumlah utas dapat mengakses metode yang tidak disinkronkan ini pada saat yang sama ketika utas lain dalam metode yang disinkronkan dari objek yang sama dan dapat membuat perubahan pada variabel instan. Untuk misalnya: -

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

Anda perlu menghindari skenario bahwa metode yang tidak disinkronkan mengakses variabel instan dan mengubahnya jika tidak ada gunanya menggunakan metode yang disinkronkan.

Dalam skenario di bawah ini: -

class X {

        private int a;
        private int b;

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

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

Hanya satu utas yang dapat berupa metode addA atau addB tetapi pada saat yang sama sejumlah utas dapat memasukkan metode changeState. Tidak ada dua utas yang dapat memasukkan addA dan addB pada saat yang sama (karena penguncian tingkat Objek) tetapi pada saat yang sama sejumlah utas dapat masuk ke changeState.

Vicky yang loyal
sumber
3

Anda dapat melakukan sesuatu seperti berikut ini. Dalam hal ini Anda menggunakan kunci pada a dan b untuk menyinkronkan alih-alih kunci pada "ini". Kami tidak dapat menggunakan int karena nilai primitif tidak memiliki kunci, jadi kami menggunakan Integer.

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

Ya, itu akan memblokir metode lain karena metode yang disinkronkan berlaku untuk objek kelas WHOLE seperti yang ditunjukkan .... tapi bagaimanapun itu akan memblokir eksekusi utas lainnya SAJA sambil melakukan penjumlahan dalam metode apa pun, addA atau addB yang dimasukkan, karena ketika selesai ... satu utas akan GRATIS objek dan utas lainnya akan mengakses metode lainnya dan seterusnya berfungsi dengan baik.

Maksud saya "disinkronkan" dibuat tepat untuk memblokir utas lainnya dari mengakses yang lain saat dalam eksekusi kode tertentu. JADI AKHIRNYA KODE INI AKAN BEKERJA HALUS.

Sebagai catatan akhir, jika ada variabel 'a' dan 'b', bukan hanya variabel unik 'a' atau nama lain apa pun, tidak perlu menyinkronkan metode ini karena sangat aman mengakses var lain (Memori lain lokasi).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Akan bekerja juga

Jose Velandia
sumber
2

Contoh ini (meskipun tidak cantik) dapat memberikan lebih banyak wawasan tentang mekanisme penguncian. Jika incrementA yang disinkronkan , dan incrementB yang tidak disinkronkan , maka incrementB akan dieksekusi secepatnya, tetapi jika incrementB juga disinkronkan maka harus 'menunggu' untuk incrementA sampai akhir, sebelum incrementB dapat melakukan tugasnya.

Kedua metode dipanggil ke objek - instance tunggal, dalam contoh ini adalah: pekerjaan , dan utas 'bersaing' adalah aThread dan utama .

Coba dengan ' disinkronkan ' dalam kenaikan B dan tanpa itu dan Anda akan melihat hasil yang berbeda. Jika kenaikan B ' disinkronkan ' juga maka harus menunggu kenaikan () untuk menyelesaikan. Jalankan beberapa kali setiap varian.

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();
    }
}
Nenad Bulatovic
sumber
1

Dalam sinkronisasi java, jika utas ingin masuk ke dalam metode sinkronisasi, ia akan mendapatkan kunci pada semua metode yang disinkronkan objek itu tidak hanya pada satu metode disinkronkan yang digunakan utas. Jadi thread yang mengeksekusi addA () akan mendapatkan kunci pada addA () dan addB () karena keduanya disinkronkan. Jadi utas lainnya dengan objek yang sama tidak dapat menjalankan addB ().

Sreedhar Reddy
sumber
0

Ini mungkin tidak bekerja karena tinju dan autoboxing dari Integer ke int dan sebaliknya juga tergantung pada JVM dan ada kemungkinan besar bahwa dua nomor yang berbeda mungkin di-hash ke alamat yang sama jika mereka berada di antara -128 dan 127.

Sriharsha grv
sumber