Java Singleton dan Sinkronisasi

117

Harap klarifikasi pertanyaan saya tentang Singleton dan Multithreading:

  • Apa cara terbaik untuk mengimplementasikan Singleton di Java, dalam lingkungan multithread?
  • Apa yang terjadi ketika beberapa utas mencoba mengakses getInstance() metode pada saat yang bersamaan?
  • Bisakah kita membuat single getInstance() synchronized?
  • Apakah sinkronisasi benar-benar diperlukan, saat menggunakan kelas Singleton?
RickDavis
sumber

Jawaban:

211

Ya, itu perlu. Ada beberapa metode yang dapat Anda gunakan untuk mencapai keamanan thread dengan inisialisasi yang lambat:

Sinkronisasi draconian:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Solusi ini mengharuskan setiap utas disinkronkan ketika pada kenyataannya hanya beberapa utas yang perlu disinkronkan.

Periksa kembali sinkronisasi :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Solusi ini memastikan bahwa hanya beberapa utas pertama yang mencoba memperoleh tunggal Anda yang harus melalui proses memperoleh kunci.

Inisialisasi sesuai Permintaan :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Solusi ini memanfaatkan jaminan model memori Java tentang inisialisasi kelas untuk memastikan keamanan thread. Setiap kelas hanya dapat dimuat sekali, dan hanya akan dimuat saat diperlukan. Artinya, pertama kali getInstancedipanggil, InstanceHolderakan dimuat dan instanceakan dibuat, dan karena ini dikontrol oleh ClassLoaders, tidak diperlukan sinkronisasi tambahan.

Jeffrey
sumber
23
Peringatan - hati-hati dengan sinkronisasi yang dicentang ulang. Ini tidak bekerja dengan baik dengan JVM pra-Java 5 karena "masalah" dengan model memori.
Stephen C
3
-1 Draconian synchronizationdan Double check synchronizationgetInstance () - metode harus statis!
Grim
2
@PeterRader Mereka tidak perlu menjadi static, tapi mungkin lebih masuk akal jika mereka. Diubah sesuai permintaan.
Jeffrey
4
Penerapan penguncian yang diperiksa ganda tidak dijamin akan berhasil. Ini sebenarnya dijelaskan dalam artikel yang Anda kutip untuk penguncian centang ganda. :) Ada contoh di sana menggunakan volatile yang bekerja dengan baik untuk 1.5 dan lebih tinggi (penguncian dua kali lipat hanya rusak di bawah 1.5). Inisialisasi pada pemegang permintaan yang juga dikutip dalam artikel mungkin akan menjadi solusi yang lebih sederhana dalam jawaban Anda.
stuckj
2
@MediumOne AFAIK, rtidak diperlukan untuk kebenaran. Ini hanya pengoptimalan untuk menghindari mengakses bidang volatile, karena itu jauh lebih mahal daripada mengakses variabel lokal.
Jeffrey
69

Pola ini melakukan inisialisasi lambat yang aman untuk instance tanpa sinkronisasi eksplisit!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Ini berfungsi karena ia menggunakan pemuat kelas untuk melakukan semua sinkronisasi untuk Anda secara gratis: Kelas MySingleton.Loaderpertama kali diakses di dalam getInstance()metode, sehingga Loaderkelas akan dimuat saat getInstance()dipanggil untuk pertama kali. Selanjutnya, pemuat kelas menjamin bahwa semua inisialisasi statis selesai sebelum Anda mendapatkan akses ke kelas - itulah yang memberi Anda keamanan thread.

Ini seperti sulap.

Ini sebenarnya sangat mirip dengan pola enum Jhurtado, tapi saya menemukan pola enum merupakan penyalahgunaan konsep enum (meskipun itu berhasil)

Bohemian
sumber
11
Sinkronisasi masih ada, hanya diberlakukan oleh JVM, bukan oleh programmer.
Jeffrey
@Jeffrey Anda benar tentu saja - Saya mengetik semuanya (lihat edit)
Bohemian
2
Saya mengerti bahwa tidak ada bedanya bagi JVM, saya hanya mengatakan itu membuat perbedaan bagi saya sejauh kode yang didokumentasikan sendiri berjalan. Saya belum pernah melihat huruf besar semua di Java tanpa kata kunci "terakhir" sebelumnya (atau enum), mendapat sedikit disonansi kognitif. Untuk seseorang yang memprogram Java secara penuh, mungkin tidak akan membuat perbedaan, tetapi jika Anda melompati bahasa bolak-balik, akan membantu jika eksplisit. Ditto untuk pemula. Meskipun, saya yakin seseorang dapat beradaptasi dengan gaya ini dengan cukup cepat; huruf besar semua mungkin cukup. Tidak bermaksud memilih nit, saya menyukai posting Anda.
Ruby
1
Jawaban yang sangat bagus, meskipun saya tidak mendapatkan sebagian darinya. Dapatkah Anda menjelaskan lebih lanjut, "Lebih lanjut, pemuat kelas menjamin bahwa semua inisialisasi statis selesai sebelum Anda mendapatkan akses ke kelas - itulah yang memberi Anda keamanan thread." , bagaimana hal itu membantu dalam keamanan thread, saya agak bingung tentang itu.
gaurav jain
2
@ wz366 sebenarnya, meskipun tidak perlu, saya setuju untuk alasan gaya (karena ini final secara efektif karena tidak ada kode lain yang dapat mengaksesnya) finalharus ditambahkan. Selesai.
Bohemian
21

Jika Anda bekerja pada lingkungan multithread di Java dan perlu menjamin semua utas tersebut mengakses satu instance kelas, Anda dapat menggunakan Enum. Ini akan memiliki keuntungan tambahan untuk membantu Anda menangani serialisasi.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

dan kemudian minta utas Anda menggunakan contoh Anda seperti:

Singleton.SINGLE.myMethod();
jhurtado.dll
sumber
8

Ya, Anda perlu melakukan getInstance()sinkronisasi. Jika tidak, mungkin akan timbul situasi di mana beberapa contoh kelas dapat dibuat.

Pertimbangkan kasus di mana Anda memiliki dua utas yang memanggil getInstance()pada saat yang bersamaan. Sekarang bayangkan T1 dijalankan setelah instance == nullpemeriksaan, dan kemudian T2 berjalan. Pada titik ini, instance tidak dibuat atau disetel, jadi T2 akan lulus pemeriksaan dan membuat instance. Sekarang bayangkan eksekusi beralih kembali ke T1. Sekarang singleton telah dibuat, tetapi T1 telah melakukan pemeriksaan! Ini akan melanjutkan untuk membuat objek lagi! MembuatgetInstance() sinkronisasi mencegah masalah ini.

Ada beberapa cara untuk membuat lajang aman thread, tetapi membuat getInstance()sinkronisasi mungkin yang paling sederhana.

Oleksi
sumber
Akankah membantu dengan meletakkan kode pembuatan objek di blok Synchronized, daripada membuat seluruh metode sinkronisasi?
RickDavis
@RaoG Tidak. Anda menginginkan pemeriksaan dan pembuatan di blok sinkronisasi. Anda perlu kedua operasi tersebut terjadi bersamaan tanpa gangguan atau situasi yang saya jelaskan di atas mungkin terjadi.
Oleksi
7

Enum tunggal

Cara termudah untuk mengimplementasikan Singleton yang aman untuk thread menggunakan Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Kode ini berfungsi sejak diperkenalkannya Enum di Java 1.5

Penguncian diperiksa ganda

Jika Anda ingin membuat kode tunggal "klasik" yang berfungsi di lingkungan multithread (mulai dari Java 1.5), Anda harus menggunakan yang ini.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

Ini bukan thread-safe sebelum 1.5 karena implementasi kata kunci volatile berbeda.

Pemuatan awal Singleton (bekerja bahkan sebelum Java 1.5)

Implementasi ini membuat instance tunggal saat kelas dimuat dan menyediakan keamanan thread.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Dan Moldovan
sumber
2

Anda juga dapat menggunakan blok kode statis untuk membuat instance instance pada pemuatan kelas dan mencegah masalah sinkronisasi thread.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}
usha
sumber
@Vimsha Beberapa hal lainnya. 1. Anda harus membuat instancefinal 2. Anda harus membuat getInstance()statis.
John Vint
Apa yang akan Anda lakukan jika Anda ingin membuat utas dalam bentuk tunggal.
Arun George
@ arun-george gunakan kumpulan utas, kumpulan utas tunggal jika diperlukan, dan kelilingi dengan sementara (true) -try-catch-throwable jika Anda ingin memastikan utas Anda tidak pernah mati, tidak peduli kesalahan apa?
tgkprog
0

Apa cara terbaik untuk mengimplementasikan Singleton di Java, dalam lingkungan multithread?

Lihat posting ini untuk cara terbaik menerapkan Singleton.

Apa cara yang efisien untuk mengimplementasikan pola tunggal di Java?

Apa yang terjadi ketika beberapa utas mencoba mengakses metode getInstance () pada saat yang bersamaan?

Itu tergantung pada cara Anda mengimplementasikan metode. Jika Anda menggunakan penguncian ganda tanpa variabel volatile, Anda mungkin mendapatkan objek Singleton yang dibangun sebagian.

Lihat pertanyaan ini untuk lebih jelasnya:

Mengapa volatile digunakan dalam contoh penguncian centang ganda ini

Bisakah kita membuat getInstance () singleton disinkronkan?

Apakah sinkronisasi benar-benar diperlukan, saat menggunakan kelas Singleton?

Tidak diperlukan jika Anda mengimplementasikan Singleton dengan cara di bawah ini

  1. intitalisasi statis
  2. enum
  3. LazyInitalaization dengan Inisialisasi-on-demand_holder_idiom

Lihat pertanyaan ini untuk detail lebih lanjut

Pola Desain Java Singleton: Pertanyaan

Ravindra babu
sumber
0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Sumber: Java Efektif -> Item 2

Ini menyarankan untuk menggunakannya, jika Anda yakin bahwa kelas akan selalu tetap tunggal.

Ashish
sumber