0Pricing
Java Academy · บทเรียน

Singleton: การใช้งานที่ปลอดภัยต่อเธรด

สร้าง Singleton ที่ปลอดภัยต่อเธรดด้วยการล็อกตรวจสอบซ้ำสองครั้ง enum หรือรูปแบบ holder

Singleton: การใช้งานที่ปลอดภัยต่อเธรด เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

รูปแบบซิงเกิลตัน

ซิงเกิลตันรับประกันว่าจะมีอินสแตนซ์ของคลาสอยู่เพียงหนึ่งเดียวใน JVM กรณีใช้งานที่พบบ่อย ได้แก่ ตัวจัดการการกำหนดค่า กลุ่มการเชื่อมต่อ และบริการ logging

ซิงเกิลตันแบบพื้นฐาน — ไม่ปลอดภัยต่อเธรด

ซิงเกิลตันแบบเริ่มต้นเมื่อจำเป็นแบบดั้งเดิมทำงานผิดพลาดเมื่อมีการเข้าถึงพร้อมกัน: สองเธรดอาจผ่านการตรวจสอบค่าว่างพร้อมกันและสร้างสองอินสแตนซ์ขึ้นมาได้

public class Config {
    private static Config instance;
    private Config() {}
    public static Config getInstance() {
        if (instance == null) {              // race condition!
            instance = new Config();
        }
        return instance;
    }
}

เมธอดแบบซิงโครไนซ์ — เรียบง่ายแต่ช้า

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

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

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

ตรวจสอบโดยไม่ล็อกก่อน แล้วตรวจสอบอีกครั้งภายในบล็อก synchronized ต้องใช้ volatile กับฟิลด์เพื่อป้องกันการสลับลำดับคำสั่ง

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

Holder สำหรับการเริ่มต้นเมื่อจำเป็น

ซิงเกิลตันแบบเริ่มต้นเมื่อจำเป็นที่ปลอดภัยและมีประสิทธิภาพที่สุดคือ ใช้คลาสซ้อนภายในแบบ static ที่เป็น private เก็บอินสแตนซ์ไว้ JVM จะโหลดคลาสนี้เมื่อมีการเรียก getInstance() ครั้งแรกเท่านั้น — การโหลดคลาสปลอดภัยต่อเธรดโดยธรรมชาติ

public class Config {
    private Config() {}
    private static final class Holder {
        static final Config INSTANCE = new Config();
    }
    public static Config getInstance() {
        return Holder.INSTANCE;
    }
}

ซิงเกิลตันแบบ enum

หนังสือ Effective Java แนะนำให้ใช้ enum ที่มีค่าคงที่เดียว วิธีนี้กระชับ ปลอดภัยต่อเธรด ปลอดภัยต่อการซีเรียลไลซ์ และไม่เสี่ยงต่อการโจมตีด้วยรีเฟลกชัน

public enum AppConfig {
    INSTANCE;
    private String apiUrl = "https://api.example.com";
    public String getApiUrl() { return apiUrl; }
}
// Usage:
String url = AppConfig.INSTANCE.getApiUrl();

การเริ่มต้นทันที

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

public class Config {
    private static final Config INSTANCE = new Config();
    private Config() {}
    public static Config getInstance() { return INSTANCE; }
}

ซิงเกิลตันกับการซีเรียลไลซ์

ซิงเกิลตันที่ผ่านการซีเรียลไลซ์แล้วดีซีเรียลไลซ์จะสร้างอินสแตนซ์ใหม่ เว้นแต่คุณจะเพิ่ม readResolve() ที่คืนค่า INSTANCE แนวทางแบบ enum จัดการเรื่องนี้ให้โดยอัตโนมัติ

private Object readResolve() { return INSTANCE; }

ซิงเกิลตันกับการโจมตีด้วยรีเฟลกชัน

รีเฟลกชันสามารถเรียกใช้คอนสตรักเตอร์ private ได้ ให้ป้องกันโดยโยนข้อยกเว้นจากคอนสตรักเตอร์หากมีอินสแตนซ์อยู่แล้ว ซิงเกิลตันแบบ enum ป้องกันปัญหานี้ได้ตามข้อกำหนดของ JVM

private Config() {
    if (Holder.INSTANCE != null) {
        throw new IllegalStateException("Use getInstance()");
    }
}

ซิงเกิลตันในเฟรมเวิร์กการฉีดการพึ่งพา

ใน Spring บีนจะเป็นซิงเกิลตันโดยค่าเริ่มต้น (@Scope("singleton")) โดยทั่วไปคุณแทบไม่ต้องใช้รูปแบบซิงเกิลตันด้วยตนเอง ให้คอนเทนเนอร์ DI จัดการแทน

@Component  // Spring manages a single instance
public class FeatureFlags {
    private final boolean darkMode = true;
    public boolean isDarkMode() { return darkMode; }
}

การทดสอบซิงเกิลตัน

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

ตรวจสอบความเข้าใจอย่างรวดเร็ว

แนวทางซิงเกิลตันใดที่ Effective Java แนะนำ

สรุปทบทวน

สำหรับซิงเกิลตันที่ปลอดภัยต่อเธรด ให้ใช้แนวทาง Holder สำหรับการเริ่มต้นเมื่อจำเป็นหรือแนวทางแบบ enum หลีกเลี่ยงการเริ่มต้นเมื่อจำเป็นแบบพื้นฐาน ในแอป Spring ให้เฟรมเวิร์กจัดการขอบเขตแทน

คำถามที่พบบ่อย

บทเรียน “Singleton: การใช้งานที่ปลอดภัยต่อเธรด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “Singleton: การใช้งานที่ปลอดภัยต่อเธรด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “Singleton: การใช้งานที่ปลอดภัยต่อเธรด”

สร้าง Singleton ที่ปลอดภัยต่อเธรดด้วยการล็อกตรวจสอบซ้ำสองครั้ง enum หรือรูปแบบ holder คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “Singleton: การใช้งานที่ปลอดภัยต่อเธรด” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. Singleton: การใช้งานที่ปลอดภัยต่อเธรด
  2. รูปแบบ Factory Method
  3. Abstract Factory สำหรับตระกูลผลิตภัณฑ์
  4. รูปแบบ Builder กับ Fluent API
← กลับไปที่ Java Academy