Apakah ada keuntungan menggunakan Metode Tersinkronisasi daripada Blok yang Disinkronkan?


401

Adakah yang bisa memberi tahu saya keuntungan metode tersinkronisasi dari blok tersinkronisasi dengan contoh?




1
@cletus pertanyaan ini sama sekali berbeda dari stackoverflow.com/questions/442564/…
Yukio Fukuzawa

Jawaban:


431

Adakah yang bisa memberi tahu saya keuntungan dari metode yang disinkronkan dari blok yang disinkronkan dengan sebuah contoh? Terima kasih.

Tidak ada keuntungan jelas menggunakan metode tersinkronisasi di atas blok.

Mungkin satu-satunya (tapi saya tidak akan menyebutnya keuntungan) adalah Anda tidak perlu memasukkan referensi objek this.

Metode:

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

Blok:

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

Lihat? Tidak ada keuntungan sama sekali.

Blok memang memiliki keunggulan dibandingkan metode, sebagian besar dalam fleksibilitas karena Anda dapat menggunakan objek lain sebagai kunci sedangkan sinkronisasi metode akan mengunci seluruh objek.

Membandingkan:

// 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) { 
        ... 
    }
}

Juga jika metode ini bertambah, Anda masih dapat memisahkan bagian yang disinkronkan:

 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
Manfaat bagi konsumen API adalah bahwa menggunakan kata kunci yang disinkronkan dalam deklarasi metode juga secara eksplisit menyatakan bahwa metode disinkronkan pada instance objek dan (mungkin) aman-thread.
Scrubbie

59
Saya tahu ini adalah pertanyaan lama tetapi menyinkronkan "ini" dianggap di beberapa kalangan sebagai anti-pola. Konsekuensi yang tidak diinginkan adalah bahwa di luar kelas seseorang dapat mengunci referensi objek yang sama dengan "ini" dan mencegah utas lainnya melewati penghalang di dalam kelas yang berpotensi menciptakan situasi kebuntuan. Membuat "private final Object = Object baru ();" variabel murni untuk tujuan penguncian adalah solusi yang sering digunakan. Inilah pertanyaan lain yang berkaitan langsung dengan masalah ini.
justin.hughey

30
"sedangkan menyinkronkan metode akan mengunci seluruh kelas." Ini tidak benar. Itu tidak mengunci kelas lengkap, tetapi contoh lengkap. Banyak objek dari kelas yang sama menahan semua kuncinya sendiri. :) Salam
codepleb

4
Sesuatu yang menarik tentang ini adalah bahwa menggunakan metode yang disinkronkan akan menyebabkan bytecode yang dihasilkan memiliki 1 instruksi lebih sedikit, karena metode memiliki bit yang disinkronkan dimasukkan ke dalam tanda tangan mereka. Karena panjang bytecode adalah faktor dalam apakah suatu metode menjadi in-line, memindahkan blok ke metode signature bisa menjadi perbedaan dalam keputusan. Secara teori sih. Saya tidak akan mendasarkan keputusan desain pada instruksi bytecode tunggal yang diselamatkan, itu sepertinya ide yang buruk. Tapi tetap, itu adalah perbedaan. =)
corsiKa

2
@corsiKa: Anda menyimpan lebih dari satu instruksi. Sebuah synchronizedblok diimplementasikan dengan menggunakan dua instruksi, monitorenterdan monitorexit, ditambah handler pengecualian yang menjamin bahwa monitorexitdisebut bahkan dalam kasus luar biasa. Itu semua disimpan saat menggunakan synchronizedmetode.
Holger

139

Satu-satunya perbedaan nyata adalah bahwa blok yang disinkronkan dapat memilih objek yang disinkronkan. Metode yang disinkronkan hanya dapat menggunakan 'this'(atau instance Kelas yang sesuai untuk metode kelas yang disinkronkan). Misalnya, ini setara secara semantik:

synchronized void foo() {
  ...
}

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

Yang terakhir ini lebih fleksibel karena dapat bersaing untuk mengunci terkait objek apa pun , sering kali variabel anggota. Ini juga lebih terperinci karena Anda bisa memiliki kode bersamaan mengeksekusi sebelum dan sesudah blok tetapi masih dalam metode. Tentu saja, Anda dapat dengan mudah menggunakan metode yang disinkronkan dengan refactoring kode konkuren menjadi metode terpisah yang tidak disinkronkan. Gunakan mana saja yang membuat kode lebih mudah dipahami.


Yang terakhir juga dapat memiliki prestasi jika tidak semua kode di foo () perlu disinkronkan.
Evan

1
Ini benar, tetapi bukan yang ditanyakan oleh "Prajurit": "Keuntungan metode tersinkronisasi" tidak ada.
OscarRyz

76

Metode Tersinkronisasi

Pro:

  • IDE Anda dapat menunjukkan metode yang disinkronkan.
  • Sintaksnya lebih ringkas.
  • Pasukan untuk membagi blok yang disinkronkan ke metode yang terpisah.

Cons:

  • Disinkronkan dengan ini dan memungkinkan orang luar untuk menyinkronkannya juga.
  • Lebih sulit untuk memindahkan kode di luar blok yang disinkronkan.

Blok yang disinkronkan

Pro:

  • Mengizinkan menggunakan variabel pribadi untuk kunci dan karenanya memaksa kunci untuk tetap berada di dalam kelas.
  • Blok yang disinkronkan dapat ditemukan dengan mencari referensi ke variabel.

Cons:

  • Sintaksnya lebih rumit sehingga membuat kode lebih sulit dibaca.

Secara pribadi saya lebih suka menggunakan metode yang disinkronkan dengan kelas yang difokuskan hanya untuk hal yang membutuhkan sinkronisasi. Kelas tersebut harus sekecil mungkin dan karenanya harus mudah untuk meninjau sinkronisasi. Orang lain tidak perlu peduli dengan sinkronisasi.


Ketika Anda mengatakan "tetap di dalam kelas" maksud Anda "tetap di dalam objek ", atau apakah saya kehilangan sesuatu?
OldPeculier

36

Perbedaan utama adalah bahwa jika Anda menggunakan blok yang disinkronkan Anda dapat mengunci pada objek selain ini yang memungkinkan untuk menjadi jauh lebih fleksibel.

Asumsikan Anda memiliki antrian pesan dan banyak produsen dan konsumen pesan. Kami tidak ingin produsen saling mengganggu, tetapi konsumen harus dapat mengambil pesan tanpa harus menunggu produsen. Jadi kami hanya membuat objek

Object writeLock = new Object();

Dan mulai sekarang setiap kali seorang produsen ingin menambahkan pesan baru, kami hanya menguncinya:

synchronized(writeLock){
  // do something
}

Jadi konsumen mungkin masih membaca, dan produsen akan dikunci.


2
Contoh Anda terbatas pada bacaan yang tidak merusak. Jika pembacaan menghapus pesan dari antrian, ini akan gagal jika dilakukan pada suatu waktu ketika produser menulis ke antrian.
ceving

30

Metode yang disinkronkan

Metode yang disinkronkan memiliki dua efek.
Pertama, ketika satu utas mengeksekusi metode yang disinkronkan untuk suatu objek, semua utas lain yang memanggil metode yang disinkronkan untuk blok objek yang sama (menunda eksekusi) sampai utas pertama selesai dengan objek.

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

Perhatikan bahwa konstruktor tidak dapat disinkronkan - menggunakan kata kunci yang disinkronkan dengan konstruktor adalah kesalahan sintaksis. Menyinkronkan konstruktor tidak masuk akal, karena hanya utas yang membuat objek harus memiliki akses ke sana ketika sedang dibangun.

Pernyataan Tersinkronisasi

Tidak seperti metode yang disinkronkan, pernyataan yang disinkronkan harus menentukan objek yang menyediakan kunci intrinsik: Paling sering saya menggunakan ini untuk menyinkronkan akses ke daftar atau peta tetapi saya tidak ingin memblokir akses ke semua metode objek.

T: Kunci dan Sinkronisasi Intrinsik Sinkronisasi dibangun di sekitar entitas internal yang dikenal sebagai kunci intrinsik atau kunci monitor. (Spesifikasi API sering merujuk ke entitas ini hanya sebagai "monitor.") Kunci intrinsik berperan dalam kedua aspek sinkronisasi: menegakkan akses eksklusif ke keadaan objek dan membangun hubungan sebelum terjadi yang penting untuk visibilitas.

Setiap objek memiliki kunci intrinsik yang terkait dengannya. Dengan konvensi, utas yang memerlukan akses eksklusif dan konsisten ke bidang objek harus memperoleh kunci intrinsik objek sebelum mengaksesnya, dan kemudian melepaskan kunci intrinsik ketika dilakukan dengan mereka. Sebuah thread dikatakan memiliki kunci intrinsik antara waktu kunci tersebut diperoleh dan melepaskan kunci tersebut. Selama utas memiliki kunci intrinsik, tidak ada utas lain yang bisa mendapatkan kunci yang sama. Utas lainnya akan memblokir ketika mencoba untuk mendapatkan kunci.

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();
    }
}

Periksa silang keluaran yang berbeda dengan metode yang disinkronkan, blokir dan tanpa sinkronisasi.


10
+1 sebagai satu-satunya yang sejauh ini menyebutkan bahwa konstruktor tidak dapat disinkronkan . Yaitu, dalam konstruktor Anda benar-benar hanya memiliki satu opsi: Blok disinkronkan.
ef2011

Saya menguji kode Anda seperti yang diarahkan tetapi C selalu 0, lalu -2024260031 dan satu-satunya hal yang mengubahnya adalah kode hash. Perilaku apa yang harus dilihat?
Justin Johnson

Anda harus mengutip artikel di bawah ini dari mana konten telah disediakan: docs.oracle.com/javase/tutorial/essential/concurrency/… dan docs.oracle.com/javase/tutorial/essential/concurrency/…
Ravindra babu

29

Catatan: metode dan blok tersinkronisasi statis berfungsi pada objek Class.

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

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

18

Ketika kompiler java mengubah kode sumber Anda menjadi kode byte, ia menangani metode yang disinkronkan dan blok yang disinkronkan sangat berbeda.

Ketika JVM mengeksekusi metode yang disinkronkan, thread yang mengeksekusi mengidentifikasi bahwa metode method_info metode memiliki set flag ACC_SYNCHRONIZED, kemudian secara otomatis memperoleh kunci objek, memanggil metode, dan melepaskan kunci. Jika pengecualian terjadi, utas secara otomatis melepaskan kunci.

Menyinkronkan blok metode, di sisi lain, memotong dukungan bawaan JVM untuk memperoleh kunci objek dan penanganan pengecualian dan mengharuskan fungsi dituliskan secara eksplisit dalam kode byte. Jika Anda membaca kode byte untuk metode dengan blok yang disinkronkan, Anda akan melihat lebih dari selusin operasi tambahan untuk mengelola fungsi ini.

Ini menunjukkan panggilan untuk menghasilkan metode yang disinkronkan dan blok yang disinkronkan:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

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

The synchronizedMethodGet()Metode menghasilkan kode byte berikut:

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

Dan inilah kode byte dari synchronizedBlockGet()metode ini:

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

Salah satu perbedaan yang signifikan antara metode dan blok yang disinkronkan adalah bahwa, Blok yang disinkronkan umumnya mengurangi cakupan kunci. Karena ruang lingkup kunci berbanding terbalik dengan kinerja, selalu lebih baik untuk mengunci hanya bagian penting dari kode. Salah satu contoh terbaik menggunakan blok tersinkronisasi adalah penguncian ganda diperiksa dalam pola Singleton di mana alih-alih mengunci seluruh getInstance()metode, kami hanya mengunci bagian kode yang penting yang digunakan untuk membuat instance Singleton. Ini meningkatkan kinerja secara drastis karena penguncian hanya diperlukan satu atau dua kali.

Saat menggunakan metode yang disinkronkan, Anda harus berhati-hati jika Anda mencampur metode tersinkronisasi statis dan non-statis.


1
Jika kita melihat metode sinkronisasi bytecode, bytecode lebih ringkas dan sederhana, jadi mengapa tidak lebih cepat dari blok sinkronisasi yang disinkronkan itu?
eatSleepCode

@eatSleepCode Perhatikan bahwa ini bytecode yang selanjutnya "dikompilasi" oleh JVM. JVM akan menambahkan yang diperlukan monitorenterdan monitorexitsebelum menjalankan kode.
Philip Couling

12

Paling sering saya menggunakan ini untuk menyinkronkan akses ke daftar atau peta tetapi saya tidak ingin memblokir akses ke semua metode objek.

Dalam kode berikut satu utas memodifikasi daftar tidak akan memblokir menunggu utas yang mengubah peta. Jika metode disinkronkan pada objek maka setiap metode harus menunggu meskipun modifikasi yang mereka buat tidak akan konflik.

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

Dengan blok yang disinkronkan, Anda dapat memiliki beberapa sinkronisasi, sehingga banyak hal yang simultan tetapi tidak bertentangan dapat berlangsung secara bersamaan.


6

Metode yang disinkronkan dapat diperiksa menggunakan API refleksi. Ini berguna untuk menguji beberapa kontrak, seperti semua metode dalam model disinkronkan .

Cuplikan berikut mencetak semua metode Hashtable yang disinkronkan:

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

5

Catatan penting tentang penggunaan blok yang disinkronkan: hati-hati apa yang Anda gunakan sebagai objek kunci!

Cuplikan kode dari user2277816 di atas mengilustrasikan hal ini karena referensi ke string literal digunakan sebagai objek penguncian. Sadarilah bahwa string literal secara otomatis diinternir di Jawa dan Anda harus mulai melihat masalahnya: setiap bagian kode yang disinkronkan pada "kunci" literal, berbagi kunci yang sama! Ini dapat dengan mudah menyebabkan kebuntuan dengan potongan kode yang sama sekali tidak terkait.

Bukan hanya objek String yang perlu Anda perhatikan. Kotak primitif juga merupakan bahaya, karena proses otomatis dan metode valueOf dapat menggunakan kembali objek yang sama, tergantung pada nilainya.

Untuk informasi lebih lanjut, lihat: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reusedreused


5

Seringkali menggunakan kunci pada level metode terlalu kasar. Mengapa mengunci sepotong kode yang tidak mengakses sumber daya bersama dengan mengunci seluruh metode. Karena setiap objek memiliki kunci, Anda dapat membuat objek tiruan untuk menerapkan sinkronisasi level blok. Level blok lebih efisien karena tidak mengunci keseluruhan metode.

Berikut beberapa contohnya

Tingkat Metode

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
 }
}

Tingkat Blok

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
 }

[Sunting]

Untuk Collectionsuka Vectordan Hashtablemereka disinkronkan saat ArrayListatau HashMaptidak dan Anda perlu mengatur kata kunci yang disinkronkan atau memanggil metode yang disinkronkan Koleksi:

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

5

Satu-satunya perbedaan: blok tersinkronisasi memungkinkan penguncian granular tidak seperti metode yang disinkronkan

Pada dasarnya synchronizedblok atau metode telah digunakan untuk menulis kode aman utas dengan menghindari kesalahan inkonsistensi memori.

Pertanyaan ini sudah sangat lama dan banyak hal telah berubah selama 7 tahun terakhir. Konstruksi pemrograman baru telah diperkenalkan untuk keamanan utas.

Anda dapat mencapai keamanan utas dengan menggunakan API konkurensi canggih, bukan synchroniedblok. Halaman dokumentasi ini menyediakan konstruksi pemrograman yang baik untuk mencapai keamanan utas.

Lock Objects mendukung idiom pengunci yang menyederhanakan banyak aplikasi bersamaan.

Pelaksana menetapkan API tingkat tinggi untuk meluncurkan dan mengelola utas. Implementasi pelaksana yang disediakan oleh java.util.concurrent menyediakan manajemen kumpulan thread yang cocok untuk aplikasi skala besar.

Koleksi Bersamaan membuatnya lebih mudah untuk mengelola koleksi data yang besar, dan dapat sangat mengurangi kebutuhan untuk sinkronisasi.

Variabel Atom memiliki fitur yang meminimalkan sinkronisasi dan membantu menghindari kesalahan konsistensi memori.

ThreadLocalRandom (dalam JDK 7) menyediakan generasi nomor pseudorandom yang efisien dari berbagai utas.

Penggantian yang lebih baik untuk disinkronkan adalah ReentrantLock , yang menggunakan LockAPI

Pengecualian mutual mutual reentrant dengan perilaku dasar dan semantik yang sama dengan kunci monitor implisit yang diakses menggunakan metode dan pernyataan yang disinkronkan, tetapi dengan kemampuan yang diperluas.

Contoh dengan kunci:

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

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

Mengacu pada java.util.concurrent dan java.util.concurrent.atomic paket juga untuk konstruksi pemrograman lainnya.

Lihat pertanyaan terkait ini juga:

Sinkronisasi vs Kunci


4

Metode yang disinkronkan digunakan untuk mengunci semua objek Blok yang disinkronkan digunakan untuk mengunci objek tertentu


3

Secara umum ini sebagian besar sama selain eksplisit tentang monitor objek yang digunakan vs objek implisit ini. Salah satu kelemahan dari metode yang disinkronkan yang saya pikir kadang-kadang diabaikan adalah bahwa dalam menggunakan referensi "ini" untuk menyinkronkan Anda membiarkan kemungkinan objek eksternal terkunci pada objek yang sama. Itu bisa menjadi bug yang sangat halus jika Anda menabraknya. Sinkronisasi pada Obyek eksplisit internal atau bidang lain yang ada dapat menghindari masalah ini, sepenuhnya merangkum sinkronisasi.


2

Seperti yang sudah dikatakan di sini, blok yang disinkronkan dapat menggunakan variabel yang ditentukan pengguna sebagai objek kunci, ketika fungsi yang disinkronkan hanya menggunakan "ini". Dan tentu saja Anda dapat memanipulasi dengan area fungsi Anda yang harus disinkronkan. Tetapi semua orang mengatakan bahwa tidak ada perbedaan antara fungsi yang disinkronkan dan blok yang mencakup seluruh fungsi menggunakan "ini" sebagai objek kunci. Itu tidak benar, perbedaannya adalah dalam kode byte yang akan dihasilkan dalam kedua situasi. Dalam hal penggunaan blok tersinkronisasi harus dialokasikan variabel lokal yang memegang referensi untuk "ini". Dan sebagai hasilnya kita akan memiliki ukuran yang sedikit lebih besar untuk fungsi (tidak relevan jika Anda hanya memiliki sedikit fungsi).

Penjelasan lebih rinci tentang perbedaan ini dapat Anda temukan di sini: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

Dalam hal metode yang disinkronkan, kunci akan diperoleh pada Obyek. Tetapi jika Anda pergi dengan blok yang disinkronkan Anda memiliki opsi untuk menentukan objek di mana kunci akan diperoleh.

Contoh:

    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

Saya tahu ini adalah pertanyaan lama, tetapi dengan cepat saya membaca tanggapan di sini, saya tidak benar-benar melihat ada yang menyebutkan bahwa kadang-kadang synchronizedmetode mungkin kunci yang salah .
Dari Java Concurrency In Practice (hal 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;
}

Kode di atas memiliki tampilan sebagai thread-safe. Namun, pada kenyataannya tidak. Dalam hal ini kunci diperoleh pada instance kelas. Namun, daftar dapat dimodifikasi oleh utas lain yang tidak menggunakan metode itu. Pendekatan yang benar adalah menggunakan

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

Kode di atas akan memblokir semua utas yang mencoba mengubah daftar dari memodifikasi daftar hingga blok yang disinkronkan selesai.


membaca buku ini saat ini ... saya bertanya-tanya ... jika daftar itu pribadi daripada publik dan hanya memiliki metode putIfAbsent, disinkronkan (ini) akan cukup kan? masalah yang dihadapi adalah karena daftar dapat dimodifikasi di luar ListHelper juga?
dtc

@dtc ya jika daftar bersifat pribadi dan tidak bocor di tempat lain di kelas maka itu sudah cukup, selama Anda menandai setiap metode lain di kelas yang memodifikasi daftar sebagai disinkronkan juga. Namun, mengunci seluruh metode alih-alih hanya Listdapat menyebabkan masalah kinerja jika ada log kode yang tidak perlu disinkronkan
aarbor

itu masuk akal. terima kasih banyak untuk menjawab! tbh, saya menemukan buku ini cukup berguna dalam memperluas pengetahuan saya dan cara mendekati multithreading tetapi juga memperkenalkan dunia kebingungan yang sama sekali baru kepada saya
dtc

2

Sebagai masalah praktis, keuntungan dari metode yang disinkronkan dari blok yang disinkronkan adalah bahwa mereka lebih tahan terhadap idiot; karena Anda tidak dapat memilih objek sewenang-wenang untuk dikunci, Anda tidak dapat menyalahgunakan sintaksis metode yang disinkronkan untuk melakukan hal-hal bodoh seperti mengunci string literal atau mengunci konten bidang yang bisa berubah yang diganti dari bawah utas.

Di sisi lain, dengan metode yang disinkronkan Anda tidak dapat melindungi kunci agar tidak didapat oleh utas apa pun yang bisa mendapatkan referensi ke objek.

Jadi menggunakan sinkronisasi sebagai pengubah pada metode lebih baik dalam melindungi pekerja sapi Anda dari menyakiti diri mereka sendiri, sementara menggunakan blok tersinkronisasi dalam hubungannya dengan objek kunci pribadi akhir lebih baik melindungi kode Anda sendiri dari pekerja sapi.


1

Dari ringkasan spesifikasi Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Pernyataan yang disinkronkan (§14.17) menghitung referensi ke objek; kemudian mencoba melakukan tindakan kunci pada objek itu dan tidak melanjutkan lebih jauh sampai tindakan kunci berhasil diselesaikan. ...

Metode yang disinkronkan (§8.4.3.5) secara otomatis melakukan aksi kunci ketika dipanggil; tubuhnya tidak dieksekusi sampai tindakan kunci berhasil diselesaikan. Jika metode ini adalah metode instance , itu mengunci kunci yang terkait dengan instance yang dipanggil (yaitu, objek yang akan dikenal sebagai ini selama eksekusi dari tubuh metode). Jika metode ini statis , ia mengunci kunci yang terkait dengan objek Kelas yang mewakili kelas di mana metode ini didefinisikan. ...

Berdasarkan uraian ini, saya akan mengatakan sebagian besar jawaban sebelumnya benar, dan metode yang disinkronkan mungkin sangat berguna untuk metode statis, di mana Anda harus mencari cara untuk mendapatkan "objek Class yang mewakili kelas di mana metode itu didefinisikan. "

Sunting: Awalnya saya pikir ini adalah kutipan dari spesifikasi Java yang sebenarnya. Mengklarifikasi bahwa halaman ini hanyalah ringkasan / penjelasan dari spesifikasi


1

TLDR; Tidak menggunakan synchronizedpengubah atau synchronized(this){...}ekspresi tetapi di synchronized(myLock){...}mana myLockbidang contoh terakhir memegang objek pribadi.


Perbedaan antara menggunakan synchronizedpengubah pada deklarasi metode dan synchronized(..){ }ekspresi dalam tubuh metode adalah ini:

  • The synchronizedpengubah ditentukan pada tanda tangan metode ini
    1. terlihat di JavaDoc yang dihasilkan,
    2. secara program ditentukan melalui refleksi ketika menguji modifier metode untuk Modifier . DINAMRONISASI ,
    3. membutuhkan lebih sedikit pengetikan dan indentasi dibandingkan dengan synchronized(this) { .... }, dan
    4. (tergantung pada IDE Anda) terlihat di garis besar kelas dan penyelesaian kode,
    5. menggunakan thisobjek sebagai kunci ketika dideklarasikan pada metode non-statis atau kelas penutup ketika dideklarasikan pada metode statis.
  • Itu synchronized(...){...} ekspresi memungkinkan Anda
    1. hanya menyinkronkan eksekusi bagian-bagian tubuh metode,
    2. untuk digunakan dalam konstruktor atau ( statis blok inisialisasi ),
    3. untuk memilih objek kunci yang mengontrol akses yang disinkronkan.

Namun, menggunakan synchronizedpengubah atau synchronized(...) {...}dengan thissebagai objek kunci (seperti dalam synchronized(this) {...}), memiliki kelemahan yang sama. Keduanya menggunakan instance itu sendiri sebagai objek kunci untuk disinkronkan. Hal ini berbahaya karena tidak hanya obyek itu sendiri tetapi setiap eksternal lainnya objek / kode yang memegang referensi ke objek yang juga dapat menggunakannya sebagai kunci sinkronisasi dengan efek samping yang berat (penurunan kinerja dan deadlock ).

Oleh karena itu praktik terbaik adalah tidak menggunakan synchronizedpengubah atau synchronized(...)ekspresi dalam hubungannya dengan thissebagai objek kunci tetapi objek kunci pribadi untuk objek ini. Sebagai contoh:

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
        }
    }
}

Anda juga dapat menggunakan beberapa objek kunci tetapi perlu perhatian khusus untuk memastikan ini tidak menghasilkan kebuntuan saat digunakan bersarang.

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

Saya kira pertanyaan ini adalah tentang perbedaan antara Thread Safe Singleton dan inisialisasi Lazy dengan penguncian cek ganda . Saya selalu merujuk ke artikel ini ketika saya perlu menerapkan beberapa singleton spesifik.

Nah, ini adalah Singleton Safe Thread :

// 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; 
  } 
} 

Pro:

  1. Inisialisasi malas dimungkinkan.

  2. Itu aman dari utas.

Cons:

  1. Metode getInstance () disinkronkan sehingga menyebabkan kinerja lambat karena beberapa utas tidak dapat mengaksesnya secara bersamaan.

Ini adalah inisialisasi Malas dengan penguncian periksa ganda :

// 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; 
  } 
} 

Pro:

  1. Inisialisasi malas dimungkinkan.

  2. Ini juga aman.

  3. Performa berkurang karena kata kunci yang disinkronkan diatasi.

Cons:

  1. Pertama kali, itu dapat mempengaruhi kinerja.

  2. Sebagai kontra. metode penguncian periksa ganda tertahankan sehingga dapat digunakan untuk aplikasi multi-ulir berkinerja tinggi.

Silakan merujuk ke artikel ini untuk lebih jelasnya:

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


-3

Sinkronisasi dengan utas. 1) JANGAN PERNAH menggunakan disinkronkan (ini) di utas itu tidak berfungsi. Sinkronisasi dengan (ini) menggunakan utas saat ini sebagai objek utas pengunci. Karena setiap utas tidak tergantung pada utas lainnya, tidak ada koordinasi sinkronisasi. 2) Pengujian kode menunjukkan bahwa pada Java 1.6 pada Mac metode sinkronisasi tidak berfungsi. 3) disinkronkan (lockObj) di mana lockObj adalah objek bersama dari semua utas yang disinkronkan akan berfungsi. 4) ReenterantLock.lock () dan .unlock () berfungsi. Lihat tutorial Java untuk ini.

Kode berikut menunjukkan poin-poin ini. Ini juga mengandung Vector thread-safe yang akan diganti untuk ArrayList, untuk menunjukkan bahwa banyak utas yang menambahkan ke Vector tidak kehilangan informasi apa pun, sedangkan yang sama dengan ArrayList dapat kehilangan informasi. 0) Kode saat ini menunjukkan hilangnya informasi karena kondisi balapan A) Komentari garis berlabel A saat ini, dan batalkan komentar pada garis A di atasnya, lalu jalankan, metode kehilangan data tetapi tidak seharusnya. B) Balikkan langkah A, tanda komentar B dan // blok akhir}. Kemudian jalankan untuk melihat hasil, tidak ada kehilangan data C) Komentar B, batalkan komentar C. Jalankan, lihat sinkronisasi pada (ini) kehilangan data, seperti yang diharapkan. Tidak punya waktu untuk menyelesaikan semua variasi, harap ini membantu. Jika sinkronisasi pada (ini), atau metode sinkronisasi berfungsi, sebutkan versi Java dan OS apa yang Anda uji. Terima kasih.

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
Sinkronisasi dengan '(ini)' tidak berfungsi, dan tidak 'menggunakan utas saat ini sebagai objek sinkronisasi', kecuali jika objek saat ini adalah kelas yang memperluas Utas. -1
Marquis of Lorne
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.