Apa cara yang efisien untuk menerapkan pola tunggal di Jawa?
Apa cara yang efisien untuk menerapkan pola tunggal di Jawa?
Jawaban:
Gunakan enum:
public enum Foo {
INSTANCE;
}
Joshua Bloch menjelaskan pendekatan ini dalam pidatonya di Java Reloaded di Google I / O 2008: tautan ke video . Lihat juga slide 30-32 dari presentasinya ( effective_java_reloaded.pdf ):
Cara yang Benar untuk Menerapkan Singleton yang Berhubungan Serial
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Edit: Sebuah porsi online "Efektif Jawa" kata:
"Pendekatan ini secara fungsional setara dengan pendekatan lapangan publik, kecuali bahwa itu lebih ringkas, menyediakan mesin serialisasi secara gratis, dan memberikan jaminan kuat terhadap berbagai instantiasi, bahkan dalam menghadapi serialisasi canggih atau serangan refleksi. Sementara pendekatan ini memiliki belum diadopsi secara luas, tipe enum elemen tunggal adalah cara terbaik untuk mengimplementasikan singleton . "
Bergantung pada penggunaannya, ada beberapa jawaban "benar".
Karena java5 cara terbaik untuk melakukannya adalah dengan menggunakan enum:
public enum Foo {
INSTANCE;
}
Sebelum java5, kasus paling sederhana adalah:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
Mari kita bahas kodenya. Pertama, Anda ingin kelas menjadi final. Dalam hal ini, saya telah menggunakan final
kata kunci untuk memberi tahu pengguna bahwa itu final. Maka Anda perlu menjadikan konstruktor sebagai pribadi untuk mencegah pengguna membuat Foo mereka sendiri. Melempar pengecualian dari konstruktor mencegah pengguna untuk menggunakan refleksi untuk membuat Foo kedua. Kemudian Anda membuat private static final Foo
bidang untuk menampung satu-satunya contoh, dan public static Foo getInstance()
metode untuk mengembalikannya. Spesifikasi Java memastikan bahwa konstruktor hanya dipanggil ketika kelas pertama kali digunakan.
Ketika Anda memiliki objek yang sangat besar atau kode konstruksi yang berat DAN juga memiliki metode atau bidang statis yang dapat diakses lainnya yang mungkin digunakan sebelum instance diperlukan, maka dan hanya dengan demikian Anda perlu menggunakan inisialisasi malas.
Anda dapat menggunakan a private static class
untuk memuat instance. Kode tersebut kemudian akan terlihat seperti:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Karena baris private static final Foo INSTANCE = new Foo();
hanya dieksekusi ketika kelas FooLoader benar-benar digunakan, ini menangani instantiation yang malas, dan apakah dijamin aman untuk thread.
Ketika Anda juga ingin dapat membuat serial objek Anda, Anda perlu memastikan deserialisasi tidak akan membuat salinan.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Metode ini readResolve()
akan memastikan satu-satunya instance akan dikembalikan, bahkan ketika objek itu serial dalam menjalankan program Anda sebelumnya.
Penafian: Saya baru saja merangkum semua jawaban yang luar biasa dan menuliskannya dalam kata-kata saya.
Saat menerapkan Singleton, kami memiliki 2 opsi
1. Pemuatan malas
2. Pemuatan awal
Pemuatan malas menambah bit overhead (banyak yang harus jujur) jadi gunakanlah hanya ketika Anda memiliki objek yang sangat besar atau kode konstruksi yang berat DAN juga memiliki metode atau bidang statis yang dapat diakses lainnya yang mungkin digunakan sebelum sebuah instance dibutuhkan, kemudian dan hanya kemudian Anda perlu menggunakan inisialisasi malas. Selain itu memilih pemuatan awal adalah pilihan yang baik.
Cara paling sederhana untuk mengimplementasikan Singleton adalah
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Semuanya baik kecuali singleton yang dimuat awal. Mari kita coba tunggal dimuat malas
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
Sejauh ini bagus tapi pahlawan kita tidak akan bertahan saat bertarung sendirian dengan banyak utas jahat yang menginginkan banyak contoh pahlawan kita. Jadi mari kita lindungi dari kejahatan multi threading
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
tapi itu tidak cukup untuk melindungi pahlawan, Sungguh !!! Ini adalah yang terbaik yang bisa kita lakukan untuk membantu pahlawan kita
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
Ini disebut "idiom Penguncian Ganda". Sangat mudah untuk melupakan pernyataan volatile dan sulit untuk memahami mengapa itu perlu.
Untuk detail: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Sekarang kita yakin tentang utas kejahatan tetapi bagaimana dengan serialisasi yang kejam? Kita harus memastikan walaupun de-serialiaztion tidak ada objek baru yang dibuat
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
Metode ini readResolve()
akan memastikan satu-satunya instance akan dikembalikan, bahkan ketika objek itu serial dalam menjalankan program kami sebelumnya.
Akhirnya kami telah menambahkan perlindungan yang cukup terhadap utas dan serialisasi tetapi kode kami terlihat besar dan jelek. Mari kita beri pahlawan kita make over
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Ya, ini adalah pahlawan kami yang sama :)
Karena baris private static final Foo INSTANCE = new Foo();
hanya dieksekusi ketika kelas FooLoader
benar-benar digunakan, ini menangani instantiation yang malas,
dan apakah dijamin aman dari utas.
Dan kami telah sampai sejauh ini, di sini adalah cara terbaik untuk mencapai semua yang kami lakukan adalah cara terbaik
public enum Foo {
INSTANCE;
}
Yang secara internal akan diperlakukan seperti
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Itu dia! Tidak ada lagi rasa takut serialisasi, utas dan kode jelek. ENUMS juga singleton diinisialisasi dengan malas .
Pendekatan ini secara fungsional setara dengan pendekatan lapangan publik, kecuali bahwa itu lebih ringkas, menyediakan mesin serialisasi secara gratis, dan memberikan jaminan kuat terhadap berbagai instantiasi, bahkan dalam menghadapi serialisasi canggih atau serangan refleksi. Sementara pendekatan ini belum diadopsi secara luas, tipe enum elemen tunggal adalah cara terbaik untuk menerapkan singleton.
-Joshua Bloch dalam "Java Efektif"
Sekarang Anda mungkin telah menyadari mengapa ENUM dianggap sebagai cara terbaik untuk menerapkan Singleton dan terima kasih atas kesabaran Anda :)
Diperbarui di blog saya .
serialVersionUID
dari 0L
. Masalah ketiga: Tidak ada kustomisasi: Setiap writeObject khusus kelas, readObject, readObjectNoData, writeReplace, dan metode readResolve yang didefinisikan oleh tipe enum diabaikan selama serialisasi dan deserialisasi.
Solusi yang diposting oleh Stu Thompson berlaku di Java5.0 dan yang lebih baru. Tapi saya lebih suka tidak menggunakannya karena saya pikir itu rawan kesalahan.
Sangat mudah untuk melupakan pernyataan volatile dan sulit untuk memahami mengapa itu perlu. Tanpa volatile kode ini tidak akan aman lagi karena antipattern penguncian diperiksa ulang. Lihat lebih lanjut tentang ini dalam paragraf 16.2.4 Java Concurrency in Practice . Singkatnya: Pola ini (sebelum Java5.0 atau tanpa pernyataan volatile) dapat mengembalikan referensi ke objek Bar yang (masih) dalam keadaan salah.
Pola ini diciptakan untuk optimalisasi kinerja. Tapi ini benar-benar bukan masalah nyata lagi. Kode inisialisasi malas berikut ini cepat dan - yang lebih penting - lebih mudah dibaca.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (Dan jika getBar
disebut "terlalu dini" maka Anda akan menghadapi masalah yang sama tidak peduli bagaimana singleon diterapkan.) Anda dapat melihat pemuatan kelas yang malas dari kode di atas di sini: pastebin.com/iq2eayiR
Thread aman di Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
EDIT : Perhatikan volatile
pengubah di sini. :) Ini penting karena tanpa itu, utas lainnya tidak dijamin oleh JMM (Java Memory Model) untuk melihat perubahan nilainya. Sinkronisasi tidak menangani hal itu - itu hanya membuat serialisasi akses ke blok kode itu.
EDIT 2 : Jawaban @Bno merinci pendekatan yang direkomendasikan oleh Bill Pugh (FindBugs) dan dapat diperdebatkan dengan lebih baik. Baca dan ikutilah jawabannya.
Lupakan inisialisasi malas , itu terlalu bermasalah. Ini adalah solusi paling sederhana:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Pastikan Anda benar-benar membutuhkannya. Lakukan google untuk "anti-pola tunggal" untuk melihat beberapa argumen yang menentangnya. Saya kira tidak ada yang salah dengan itu tetapi itu hanya mekanisme untuk mengekspos beberapa sumber daya global / data jadi pastikan bahwa ini adalah cara terbaik. Secara khusus saya telah menemukan injeksi ketergantungan lebih berguna terutama jika Anda juga menggunakan tes unit karena DI memungkinkan Anda untuk menggunakan sumber daya yang diejek untuk tujuan pengujian.
Saya bingung dengan beberapa jawaban yang menyarankan DI sebagai alternatif untuk menggunakan lajang; ini adalah konsep yang tidak terkait. Anda dapat menggunakan DI untuk menyuntikkan instance tunggal atau non-tunggal (mis. Per-utas). Setidaknya ini benar jika Anda menggunakan Spring 2.x, saya tidak bisa berbicara untuk kerangka DI lainnya.
Jadi jawaban saya untuk OP adalah (dalam semua kecuali kode sampel yang paling sepele) untuk:
Pendekatan ini memberi Anda arsitektur terpisah yang bagus (dan karena itu fleksibel dan dapat diuji) di mana apakah menggunakan singleton adalah detail implementasi yang mudah dibalik (asalkan singlet yang Anda gunakan adalah threadsafe, tentu saja).
TicketNumberer
yang perlu memiliki contoh global tunggal, dan di mana Anda ingin menulis kelas TicketIssuer
yang berisi baris kode int ticketNumber = ticketNumberer.nextTicketNumber();
. Dalam pemikiran tradisional tunggal, baris kode sebelumnya harus seperti TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. Dalam berpikir DI, kelas akan memiliki konstruktor seperti public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
(atau salah satu anteknya) akan menciptakan ketergantungan dan kemudian memanggil konstruktor. Pada dasarnya, penggunaan variabel global (atau metode global) hanyalah bentuk sederhana dari pola locator layanan yang ditakuti , dan dapat diganti dengan injeksi dependensi, sama seperti penggunaan lain dari pola itu.
Sangat pertimbangkan mengapa Anda membutuhkan singleton sebelum menulisnya. Ada perdebatan semu-religius tentang penggunaannya yang bisa dengan mudah Anda temukan jika Anda menggunakan google singleton di Jawa.
Secara pribadi saya mencoba menghindari lajang sesering mungkin karena berbagai alasan, lagi-lagi sebagian besar dapat ditemukan dengan googling lajang. Saya merasa bahwa sering lajang dilecehkan karena mereka mudah dipahami oleh semua orang, mereka digunakan sebagai mekanisme untuk memasukkan data "global" ke dalam desain OO dan mereka digunakan karena mudah untuk menghindari manajemen siklus hidup objek (atau benar-benar berpikir tentang bagaimana Anda dapat melakukan A dari dalam B). Lihatlah hal-hal seperti Inversion of Control (IoC) atau Dependency Injection (DI) untuk jalan tengah yang menyenangkan.
Jika Anda benar-benar membutuhkannya maka wikipedia memiliki contoh implementasi singleton yang tepat.
Berikut adalah 3 pendekatan berbeda
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Pengecekan ganda Penguncian / pemuatan malas
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) Metode pabrik statis
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Saya menggunakan Spring Framework untuk mengelola lajang saya. Itu tidak menegakkan "keanehan" kelas (yang sebenarnya tidak bisa Anda lakukan jika ada beberapa kelas loader yang terlibat) tetapi memberikan cara yang sangat mudah untuk membangun dan mengkonfigurasi pabrik yang berbeda untuk membuat berbagai jenis objek.
Versi 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Pemuatan malas, aman dengan pemblokiran, kinerja rendah karena synchronized
.
Versi 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Pemuatan malas, aman dengan non-blocking, kinerja tinggi.
Jika Anda tidak perlu memuat malas maka cukup coba
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Jika Anda ingin pemuatan yang malas dan Anda ingin Singleton Anda aman dari benang, coba pola periksa dua kali
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Karena pola pemeriksaan ganda tidak dijamin berfungsi (karena beberapa masalah dengan kompiler, saya tidak tahu apa-apa lagi tentang itu.), Anda juga dapat mencoba menyinkronkan seluruh metode getInstance atau membuat registri untuk semua Singletons Anda.
volatile
Saya akan mengatakan Enum singleton
Singleton menggunakan enum di Jawa umumnya cara untuk mendeklarasikan enum singleton. Enum singleton dapat berisi variabel instan dan metode instance. Demi kesederhanaan, juga perhatikan bahwa jika Anda menggunakan metode instance apa pun dari yang Anda butuhkan untuk memastikan keamanan metode tersebut jika hal itu memengaruhi keadaan objek.
Penggunaan enum sangat mudah diimplementasikan dan tidak memiliki kelemahan tentang objek serial, yang harus dielakkan dengan cara lain.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Anda dapat mengaksesnya dengan Singleton.INSTANCE
, lebih mudah daripada memanggil getInstance()
metode di Singleton.
1.12 Serialisasi dari Konstanta Enum
Konstanta Enum diserialisasi secara berbeda dari objek serialable atau externalizable biasa. Bentuk serial konstanta enum hanya terdiri atas namanya; nilai bidang konstanta tidak ada dalam formulir. Untuk membuat serial suatu konstanta enum,
ObjectOutputStream
tulis nilai yang dikembalikan oleh metode nama konstanta enum. Untuk menghilangkan desalisasi konstanta enum,ObjectInputStream
baca nama konstanta dari stream; konstanta deserialized kemudian diperoleh dengan memanggiljava.lang.Enum.valueOf
metode, melewati tipe enum konstanta bersama dengan nama konstanta yang diterima sebagai argumen. Seperti objek serializable atau eksternal lainnya, konstanta enum dapat berfungsi sebagai target referensi belakang muncul kemudian dalam aliran serialisasi.Proses dimana konstanta enum yang serial tidak dapat disesuaikan: setiap kelas khusus
writeObject
,readObject
,readObjectNoData
,writeReplace
, danreadResolve
metode yang didefinisikan oleh jenis enum diabaikan selama serialisasi dan deserialization. Demikian pula, setiapserialPersistentFields
atauserialVersionUID
bidang deklarasi juga diabaikan - semua jenis enum telah tetapserialVersionUID
dari0L
. Mendokumentasikan bidang dan data berseri serial untuk jenis enum tidak perlu, karena tidak ada variasi dalam jenis data yang dikirim.
Masalah lain dengan Singletons konvensional adalah bahwa begitu Anda mengimplementasikan Serializable
antarmuka, Singleton tidak lagi tetap Singleton karena readObject()
metode selalu mengembalikan contoh baru seperti konstruktor di Jawa. Ini dapat dihindari dengan menggunakan readResolve()
dan membuang instance yang baru dibuat dengan mengganti dengan singleton seperti di bawah ini
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Ini dapat menjadi lebih kompleks jika Kelas Singleton Anda mempertahankan keadaan, karena Anda perlu membuatnya sementara, tetapi dengan di Enum Singleton, Serialisasi dijamin oleh JVM.
Baca Bagus
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Mungkin agak terlambat untuk permainan ini, tetapi ada banyak nuansa di sekitar menerapkan singleton. Pola dudukan tidak dapat digunakan dalam banyak situasi. Dan IMO saat menggunakan volatile - Anda juga harus menggunakan variabel lokal. Mari kita mulai dari awal dan beralih ke masalah. Anda akan melihat apa yang saya maksud.
Upaya pertama mungkin terlihat seperti ini:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Di sini kita memiliki kelas MySingleton yang memiliki anggota statis pribadi bernama INSTANCE, dan metode statis publik yang disebut getInstance (). Pertama kali getInstance () dipanggil, anggota INSTANCE adalah nol. Aliran kemudian akan jatuh ke dalam kondisi penciptaan dan membuat instance baru dari kelas MySingleton. Panggilan selanjutnya ke getInstance () akan menemukan bahwa variabel INSTANCE telah ditetapkan, dan karenanya tidak membuat instance MySingleton lain. Ini memastikan hanya ada satu instance MySingleton yang dibagikan di antara semua penelepon getInstance ().
Tetapi implementasi ini memiliki masalah. Aplikasi multi-utas akan memiliki kondisi balapan pada pembuatan instance tunggal. Jika beberapa utas eksekusi mengenai metode getInstance () pada (atau sekitar) pada waktu yang sama, mereka masing-masing akan melihat anggota INSTANCE sebagai nol. Ini akan menghasilkan setiap utas membuat instance MySingleton baru dan selanjutnya menetapkan anggota INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Di sini kami telah menggunakan kata kunci yang disinkronkan dalam tanda tangan metode untuk menyinkronkan metode getInstance (). Ini tentu akan memperbaiki kondisi balapan kami. Utas sekarang akan memblokir dan memasukkan metode satu per satu. Tetapi itu juga menciptakan masalah kinerja. Implementasi ini tidak hanya menyinkronkan pembuatan instance tunggal, tetapi juga menyinkronkan semua panggilan ke getInstance (), termasuk membaca. Membaca tidak perlu disinkronkan karena hanya mengembalikan nilai INSTANCE. Karena membaca akan menjadi bagian terbesar dari panggilan kami (ingat, instantiasi hanya terjadi pada panggilan pertama), kami akan mengalami performa yang tidak perlu dengan menyinkronkan seluruh metode.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Di sini kami telah memindahkan sinkronisasi dari tanda tangan metode, ke blok yang disinkronkan yang membungkus pembuatan instance MySingleton. Tetapi apakah ini menyelesaikan masalah kita? Ya, kami tidak lagi memblokir bacaan, tetapi kami juga telah mengambil langkah mundur. Beberapa utas akan menekan metode getInstance () pada atau sekitar waktu yang sama dan mereka semua akan melihat anggota INSTANCE sebagai nol. Mereka kemudian akan menekan blok yang disinkronkan di mana seseorang akan mendapatkan kunci dan membuat instance. Ketika utas itu keluar dari blok, utas lainnya akan bersaing untuk kunci, dan satu per satu setiap utas akan jatuh melalui blok dan membuat instance baru dari kelas kita. Jadi kami segera kembali ke tempat kami mulai.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Di sini kami mengeluarkan cek lain dari DALAM blok. Jika anggota INSTANCE telah ditetapkan, kami akan melewatkan inisialisasi. Ini disebut penguncian ganda.
Ini memecahkan masalah kami tentang banyak instantiasi. Tetapi sekali lagi, solusi kami telah menghadirkan tantangan lain. Utas lain mungkin tidak “melihat” bahwa anggota INSTANCE telah diperbarui. Ini karena bagaimana Java mengoptimalkan operasi memori. Utas menyalin nilai asli variabel dari memori utama ke dalam cache CPU. Perubahan nilai kemudian ditulis ke, dan dibaca dari, cache itu. Ini adalah fitur Java yang dirancang untuk mengoptimalkan kinerja. Tetapi ini menciptakan masalah bagi implementasi tunggal kami. Utas kedua - sedang diproses oleh CPU atau inti yang berbeda, menggunakan cache yang berbeda - tidak akan melihat perubahan yang dilakukan oleh yang pertama. Ini akan menyebabkan utas kedua melihat anggota INSTANCE sebagai nol yang memaksa instance baru singleton kami dibuat.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Kami menyelesaikan ini dengan menggunakan kata kunci yang tidak stabil pada pernyataan anggota INSTANCE. Ini akan memberi tahu kompiler untuk selalu membaca dari, dan menulis ke, memori utama, dan bukan cache CPU.
Tetapi perubahan sederhana ini harus dibayar. Karena kita mem-bypass cache CPU, kita akan mendapatkan hit kinerja setiap kali kita beroperasi pada anggota INSTANCE yang mudah menguap - yang kita lakukan 4 kali. Kami memeriksa ulang keberadaan (1 dan 2), mengatur nilai (3), dan kemudian mengembalikan nilai (4). Orang bisa berpendapat bahwa jalan ini adalah kasus pinggiran karena kami hanya membuat contoh selama panggilan pertama dari metode ini. Mungkin kinerja yang berhasil pada penciptaan bisa ditoleransi. Tetapi bahkan use case utama kita, berbunyi, akan beroperasi pada anggota volatile dua kali. Sekali untuk memeriksa keberadaan, dan lagi untuk mengembalikan nilainya.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Karena hit kinerja adalah karena beroperasi secara langsung pada anggota yang mudah menguap, mari kita tetapkan variabel lokal ke nilai yang tidak stabil dan beroperasi pada variabel lokal sebagai gantinya. Ini akan mengurangi berapa kali kami beroperasi pada volatile, sehingga mengambil kembali sebagian dari kinerja kami yang hilang. Perhatikan bahwa kita harus mengatur variabel lokal kita lagi ketika kita memasuki blok yang disinkronkan. Ini memastikan sudah mutakhir dengan perubahan apa pun yang terjadi saat kami sedang menunggu kunci.
Saya menulis artikel tentang ini baru-baru ini. Mendekonstruksi Singleton . Anda dapat menemukan lebih banyak info tentang contoh-contoh ini dan contoh pola "pemegang" di sana. Ada juga contoh dunia nyata yang menampilkan pendekatan volatil yang dicek ganda. Semoga ini membantu.
class MySingleton
- mungkin seharusnya final
?
BearerToken
tidak statis karena merupakan bagian dari BearerTokenFactory
- yang dikonfigurasi dengan server otorisasi tertentu. Mungkin ada banyak BearerTokenFactory
objek - masing-masing memiliki "cache" sendiri yang dibagikan BearerToken
sampai habis. The hasExpired()
metode pada BeraerToken
disebut dalam pabrik get()
metode untuk memastikan tidak membagikan sebuah kadaluarsa tanda. Jika kedaluwarsa, token baru akan diminta dari server otorisasi. Paragraf yang mengikuti blok kode menjelaskan ini secara lebih rinci.
Ini adalah cara mengimplementasikan yang sederhana singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Ini adalah cara membuat malas dengan benar singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Tetapi memang jika Anda tidak memiliki metode statis lain di kelas Singleton
Anda dan Anda hanya menelepon getInstance()
tidak ada perbedaan nyata.
Anda perlu memeriksa ulang idiom jika Anda perlu memuat variabel instan dari sebuah kelas. Jika Anda perlu memuat variabel statis atau tunggal dengan malas, Anda perlu inisilisasi pada idiom pemegang permintaan .
Selain itu, jika singleton perlu seriliazble, semua bidang lainnya harus bersifat sementara dan metode readResolve () perlu diimplementasikan untuk mempertahankan invarian objek singleton. Jika tidak, setiap kali objek di-deserialisasi, instance baru dari objek akan dibuat. Apa yang readResolve () lakukan adalah mengganti objek baru yang dibaca oleh readObject (), yang memaksa objek baru itu menjadi sampah yang dikumpulkan karena tidak ada variabel yang merujuk padanya.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Berbagai cara untuk membuat objek tunggal:
Sesuai Joshua Bloch - Enum akan menjadi yang terbaik.
Anda dapat menggunakan penguncian cek ganda juga.
Bahkan kelas statis dalam dapat digunakan.
Enum singleton
Cara paling sederhana untuk mengimplementasikan Singleton yang aman-threaded adalah 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 kode singleton "klasik" yang berfungsi di lingkungan multithreaded (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 tidak aman sebelum 1,5 karena penerapan kata kunci yang mudah menguap berbeda.
Pemuatan awal Singleton (bekerja bahkan sebelum Java 1.5)
Implementasi ini instantiates singleton ketika kelas dimuat dan memberikan 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");
}
}
Untuk JSE 5.0 dan di atasnya, gunakan pendekatan Enum, jika tidak gunakan pendekatan static singleton holder ((pendekatan lazy loading yang dijelaskan oleh Bill Pugh). Solusi terakhir juga aman untuk benang tanpa memerlukan konstruksi bahasa khusus (yaitu volatile atau disinkronkan).
Argumen lain yang sering digunakan terhadap Lajang adalah masalah testabilitas mereka. Lajang tidak mudah dipermainkan untuk tujuan pengujian. Jika ini ternyata menjadi masalah, saya ingin melakukan sedikit modifikasi berikut:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
setInstance
Metode yang ditambahkan memungkinkan pengaturan implementasi mockup dari kelas singleton selama pengujian:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Ini juga bekerja dengan pendekatan inisialisasi awal:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Ini memiliki kelemahan dalam mengekspos fungsi ini ke aplikasi normal juga. Pengembang lain yang mengerjakan kode itu bisa tergoda untuk menggunakan metode 'setInstance' untuk mengubah fungsi tertentu dan dengan demikian mengubah seluruh perilaku aplikasi, oleh karena itu metode ini harus mengandung setidaknya peringatan yang baik di javadoc-nya.
Namun, untuk kemungkinan pengujian-mockup (bila perlu), paparan kode ini mungkin merupakan harga yang dapat diterima untuk membayar.
kelas singleton paling sederhana
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Saya masih berpikir setelah java 1.5, enum adalah implementasi singleton terbaik yang tersedia karena juga memastikan bahwa bahkan di lingkungan multi-threaded - hanya satu instance yang dibuat.
public enum Singleton{
INSTANCE;
}
dan kamu selesai !!!
Lihat posting ini.
Contoh Pola Desain GoF di perpustakaan inti Jawa
Dari bagian "Singleton" jawaban terbaik,
Singleton (dapat dikenali dengan metode kreatif yang mengembalikan contoh yang sama (biasanya dengan sendirinya) setiap kali)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Anda juga dapat mempelajari contoh Singleton dari kelas asli Jawa sendiri.
Pola singleton terbaik yang pernah saya lihat menggunakan antarmuka Pemasok.
Lihat di bawah:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
Terkadang " static Foo foo = new Foo();
" sederhana tidak cukup. Pikirkan beberapa penyisipan data dasar yang ingin Anda lakukan.
Di sisi lain Anda harus menyinkronkan metode apa pun yang instantiates variabel singleton seperti itu. Sinkronisasi tidak seburuk itu, tetapi dapat menyebabkan masalah kinerja atau penguncian (dalam situasi yang sangat jarang menggunakan contoh ini. Solusinya adalah
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
Sekarang apa yang terjadi? Kelas dimuat melalui pemuat kelas. Langsung setelah kelas ditafsirkan dari byte Array, VM mengeksekusi blok {} - statis . itulah rahasianya: Blok-statis hanya dipanggil sekali, waktu kelas yang diberikan (nama) dari paket yang diberikan dimuat oleh loader satu kelas ini.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
Karena kami telah menambahkan kata kunci yang Disinkronkan sebelum getInstance, kami telah menghindari kondisi balapan dalam kasus ketika dua utas memanggil getInstance secara bersamaan.