Java Singleton และการซิงโครไนซ์


117

โปรดชี้แจงข้อสงสัยของฉันเกี่ยวกับ Singleton และ Multithreading:

  • วิธีใดเป็นวิธีที่ดีที่สุดในการนำ Singleton ไปใช้ใน Java ในสภาพแวดล้อมแบบมัลติเธรด
  • จะเกิดอะไรขึ้นเมื่อหลายเธรดพยายามเข้าถึงgetInstance() วิธีการในเวลาเดียวกัน
  • เราสามารถสร้างซิงเกิลตันได้getInstance() synchronizedหรือไม่?
  • การซิงโครไนซ์จำเป็นจริงๆหรือไม่เมื่อใช้คลาส Singleton

คำตอบ:


211

ใช่มันเป็นสิ่งที่จำเป็น มีหลายวิธีที่คุณสามารถใช้เพื่อให้เกิดความปลอดภัยของเธรดด้วยการเริ่มต้นแบบขี้เกียจ:

การซิงโครไนซ์ Draconian:

private static YourObject instance;

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

โซลูชันนี้ต้องการให้ทุกเธรดซิงโครไนซ์เมื่อในความเป็นจริงจำเป็นต้องมีเพียงไม่กี่รายการแรกเท่านั้น

ตรวจสอบการซิงโครไนซ์อีกครั้ง :

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

โซลูชันนี้ช่วยให้มั่นใจได้ว่ามีเพียงไม่กี่เธรดแรกที่พยายามจะได้ซิงเกิลตันของคุณจะต้องผ่านขั้นตอนการได้มาซึ่งการล็อก

การเริ่มต้นตามความต้องการ :

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

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

โซลูชันนี้ใช้ประโยชน์จากการรับประกันของโมเดลหน่วยความจำ Java เกี่ยวกับการเริ่มต้นคลาสเพื่อความปลอดภัยของเธรด แต่ละคลาสสามารถโหลดได้เพียงครั้งเดียวและจะโหลดเมื่อจำเป็นเท่านั้น นั่นหมายความว่าครั้งแรกgetInstanceถูกเรียกInstanceHolderจะถูกโหลดและinstanceจะถูกสร้างขึ้นและเนื่องจากสิ่งนี้ถูกควบคุมโดยClassLoaders จึงไม่จำเป็นต้องซิงโครไนซ์เพิ่มเติม


23
คำเตือน - โปรดใช้ความระมัดระวังในการซิงโครไนซ์ที่ตรวจสอบซ้ำ มันทำงานไม่ถูกต้องกับ JVM ก่อน Java 5 เนื่องจาก "ปัญหา" กับโมเดลหน่วยความจำ
Stephen C

3
-1 Draconian synchronizationและDouble check synchronizationgetInstance () - เมธอดต้องคงที่!
Grim

2
@PeterRader พวกเขาไม่จำเป็นต้องเป็นstaticแต่มันอาจจะสมเหตุสมผลกว่าถ้าพวกเขาเป็น แก้ไขเพิ่มเติมตามที่ร้องขอ
Jeffrey

4
คุณไม่รับประกันว่าจะใช้การล็อกแบบตรวจสอบซ้ำ มีการอธิบายไว้ในบทความที่คุณอ้างถึงการล็อกแบบตรวจสอบซ้ำสองครั้ง :) มีตัวอย่างในการใช้สารระเหยที่ทำงานได้อย่างถูกต้องสำหรับ 1.5 และสูงกว่า (การล็อคที่ตรวจสอบสองครั้งเป็นเพียงธรรมดาที่เสียต่ำกว่า 1.5) การเริ่มต้นตามผู้ถือความต้องการที่อ้างถึงในบทความอาจเป็นวิธีแก้ปัญหาที่ง่ายกว่าในคำตอบของคุณ
เจ

2
@MediumOne AFAIK rไม่จำเป็นสำหรับความถูกต้อง เป็นเพียงการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงการเข้าถึงฟิลด์ที่ผันผวนเนื่องจากมีราคาแพงกว่าการเข้าถึงตัวแปรภายในมาก
Jeffrey

69

รูปแบบนี้เป็นการเริ่มต้นอินสแตนซ์แบบขี้เกียจแบบปลอดภัยเธรดโดยไม่ต้องซิงโครไนซ์อย่างชัดเจน!

public class MySingleton {

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

     private MySingleton () {}

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

ทำงานได้เนื่องจากใช้ตัวโหลดคลาสเพื่อทำการซิงโครไนซ์ทั้งหมดให้คุณโดยไม่เสียค่าใช้จ่าย: คลาสMySingleton.Loaderจะถูกเข้าถึงก่อนภายในgetInstance()เมธอดดังนั้นLoaderคลาสจะโหลดเมื่อgetInstance()ถูกเรียกเป็นครั้งแรก นอกจากนี้ตัวโหลดคลาสยังรับประกันได้ว่าการเริ่มต้นแบบคงที่ทั้งหมดจะเสร็จสมบูรณ์ก่อนที่คุณจะเข้าถึงคลาสนั่นคือสิ่งที่ช่วยให้คุณปลอดภัยต่อเธรด

มันเหมือนเวทมนตร์

จริงๆแล้วมันคล้ายกับรูปแบบ enum ของ Jhurtado แต่ฉันพบว่ารูปแบบ enum เป็นการละเมิดแนวคิด enum (แม้ว่าจะใช้งานได้)


11
การซิงโครไนซ์ยังคงมีอยู่เพียงแค่บังคับใช้โดย JVM แทนที่จะเป็นโปรแกรมเมอร์
Jeffrey

@ เจฟเฟรย์คุณพูดถูกแน่นอน - ฉันพิมพ์ทั้งหมดใน (ดูการแก้ไข)
โบฮีเมียน

2
ฉันเข้าใจว่ามันไม่ได้สร้างความแตกต่างกับ JVM ฉันแค่บอกว่ามันสร้างความแตกต่างให้กับฉันเท่าที่โค้ดที่จัดทำเอกสารด้วยตัวเองไป ฉันไม่เคยเห็นตัวพิมพ์ใหญ่ทั้งหมดใน Java ที่ไม่มีคีย์เวิร์ด "final" มาก่อน (หรือ enum) มีความไม่ลงรอยกันทางปัญญาเล็กน้อย สำหรับคนที่เขียนโปรแกรม Java แบบเต็มเวลาอาจจะไม่สร้างความแตกต่าง แต่ถ้าคุณข้ามภาษาไปมาจะช่วยให้ชัดเจนได้ Ditto สำหรับมือใหม่ แม้ว่าฉันมั่นใจว่าจะสามารถปรับให้เข้ากับสไตล์นี้ได้ค่อนข้างเร็ว ตัวพิมพ์ใหญ่ทั้งหมดก็น่าจะเพียงพอแล้ว ไม่ได้ตั้งใจเลือกฉันชอบโพสต์ของคุณ
Ruby

1
คำตอบที่ยอดเยี่ยมแม้ว่าฉันจะไม่ได้รับบางส่วนก็ตาม คุณช่วยอธิบายเพิ่มเติมได้ไหม "นอกจากนี้ตัวโหลดคลาสจะรับประกันได้ว่าการเริ่มต้นแบบคงที่ทั้งหมดจะเสร็จสมบูรณ์ก่อนที่คุณจะเข้าถึงคลาสนั่นคือสิ่งที่ช่วยให้คุณมีความปลอดภัยของเธรด" วิธีที่ช่วยในเรื่องความปลอดภัยของด้ายฉันสับสนเล็กน้อยเกี่ยวกับเรื่องนี้
gaurav jain

2
@ wz366 จริง ๆ แล้วแม้ว่าจะไม่จำเป็น แต่ฉันก็เห็นด้วยสำหรับเหตุผลด้านสไตล์ (เนื่องจากเป็นที่สิ้นสุดอย่างมีประสิทธิภาพเนื่องจากไม่มีรหัสอื่นที่สามารถเข้าถึงได้) finalควรเพิ่ม เสร็จสิ้น
โบฮีเมียน

21

หากคุณกำลังทำงานบนสภาพแวดล้อมแบบมัลติเธรดใน Java และต้องการรับประกันว่าเธรดเหล่านั้นทั้งหมดกำลังเข้าถึงคลาสเดียวคุณสามารถใช้ Enum ได้ สิ่งนี้จะมีข้อได้เปรียบเพิ่มเติมในการช่วยคุณจัดการกับอนุกรม

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

จากนั้นให้เธรดของคุณใช้อินสแตนซ์ของคุณเช่น:

Singleton.SINGLE.myMethod();

8

ใช่คุณต้องทำการgetInstance()ซิงโครไนซ์ หากไม่เป็นเช่นนั้นอาจเกิดสถานการณ์ที่สามารถสร้างอินสแตนซ์ของคลาสได้หลายอินสแตนซ์

พิจารณากรณีที่คุณมีสองเธรดที่โทรgetInstance()ในเวลาเดียวกัน ลองนึกภาพ T1 ดำเนินการเพียงแค่ผ่านการinstance == nullตรวจสอบจากนั้น T2 จะทำงาน ณ เวลานี้ยังไม่ได้สร้างหรือตั้งค่าอินสแตนซ์ดังนั้น T2 จะผ่านการตรวจสอบและสร้างอินสแตนซ์ ลองนึกภาพว่าการดำเนินการเปลี่ยนกลับไปเป็น T1 ตอนนี้สร้างซิงเกิลตันแล้ว แต่ T1 ได้ทำการตรวจสอบแล้ว! มันจะดำเนินการสร้างวัตถุอีกครั้ง! การgetInstance()ซิงโครไนซ์จะช่วยป้องกันปัญหานี้

มีสองสามวิธีในการทำให้ singletons ปลอดภัย แต่การgetInstance()ซิงโครไนซ์อาจเป็นวิธีที่ง่ายที่สุด


จะช่วยโดยการใส่รหัสการสร้างวัตถุในบล็อกซิงโครไนซ์แทนที่จะทำการซิงโครไนซ์วิธีการทั้งหมดหรือไม่
RickDavis

@RaoG ไม่คุณต้องการทั้งการตรวจสอบและการสร้างในบล็อกการซิงโครไนซ์ คุณต้องการให้การดำเนินการทั้งสองนี้เกิดขึ้นพร้อมกันโดยไม่มีการหยุดชะงักมิฉะนั้นสถานการณ์ที่ฉันอธิบายไว้ข้างต้นอาจเกิดขึ้น
Oleksi

7

Enum singleton

วิธีที่ง่ายที่สุดในการใช้ Singleton ที่ปลอดภัยต่อเธรดคือการใช้ Enum

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

โค้ดนี้ใช้ได้ตั้งแต่การเปิดตัว Enum ใน Java 1.5

ตรวจสอบการล็อกสองครั้ง

หากคุณต้องการเขียนโค้ดซิงเกิลตันแบบ“ คลาสสิก” ที่ทำงานในสภาพแวดล้อมแบบมัลติเธรด (เริ่มจาก Java 1.5) คุณควรใช้อันนี้

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

สิ่งนี้ไม่ปลอดภัยต่อเธรดก่อน 1.5 เนื่องจากการใช้คีย์เวิร์ดระเหยแตกต่างกัน

การโหลด Singleton ในช่วงต้น (ใช้งานได้ก่อน Java 1.5)

การใช้งานนี้สร้างอินสแตนซ์ซิงเกิลตันเมื่อคลาสถูกโหลดและให้ความปลอดภัยของเธรด

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

}

2

คุณยังสามารถใช้บล็อกโค้ดแบบคงที่เพื่อสร้างอินสแตนซ์อินสแตนซ์ที่โหลดคลาสและป้องกันปัญหาการซิงโครไนซ์เธรด

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha คู่อื่น ๆ . 1. คุณควรทำinstanceขั้นสุดท้าย 2. คุณควรทำให้getInstance()คงที่
John Vint

คุณจะทำอย่างไรหากต้องการสร้างเธรดในซิงเกิลตัน
อรุณจอร์จ

@ arun-george ใช้เธรดพูลเธรดพูลเดียวหากจำเป็นและล้อมรอบด้วย while (จริง) - ลองจับ - โยนได้หากคุณต้องการให้แน่ใจว่าเธรดของคุณจะไม่มีวันตายไม่ว่าจะมีข้อผิดพลาดอะไร?
tgkprog

0

วิธีใดเป็นวิธีที่ดีที่สุดในการนำ Singleton ไปใช้ใน Java ในสภาพแวดล้อมแบบมัลติเธรด

อ้างถึงโพสต์นี้สำหรับวิธีที่ดีที่สุดในการนำ Singleton ไปใช้

วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลตันใน Java คืออะไร?

จะเกิดอะไรขึ้นเมื่อหลายเธรดพยายามเข้าถึงเมธอด getInstance () พร้อมกัน

ขึ้นอยู่กับวิธีที่คุณใช้วิธีนี้หากคุณใช้การล็อกสองครั้งโดยไม่มีตัวแปรระเหยคุณอาจได้รับวัตถุ Singleton ที่สร้างขึ้นบางส่วน

อ้างถึงคำถามนี้สำหรับรายละเอียดเพิ่มเติม:

เหตุใดจึงใช้สารระเหยในตัวอย่างนี้ของการล็อกแบบตรวจสอบซ้ำ

เราสามารถทำให้ getInstance () ซิงโครไนซ์ของ Singleton ได้หรือไม่?

การซิงโครไนซ์จำเป็นจริงๆหรือไม่เมื่อใช้คลาส Singleton

ไม่จำเป็นหากคุณใช้ Singleton ด้วยวิธีด้านล่าง

  1. intitalization คงที่
  2. enum
  3. LazyInitalaization กับ Initialization-on-demand_holder_idiom

อ้างถึงคำถามนี้ก่อนรายละเอียดเพิ่มเติม

รูปแบบการออกแบบ Java Singleton: คำถาม


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

ที่มา: Effective Java -> Item 2

แนะนำให้ใช้หากคุณแน่ใจว่าคลาสจะยังคงเป็นซิงเกิลตันเสมอ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.