1. โหมดซิงเกิลตันคืออะไร
รูปแบบ Singleton หมายถึงการมีอยู่เพียงครั้งเดียวตลอดชีวิตของแอปพลิเคชัน รูปแบบ Singleton เป็นรูปแบบการออกแบบที่ใช้กันอย่างแพร่หลาย มันมีประโยชน์มากมายซึ่งสามารถหลีกเลี่ยงการสร้างวัตถุอินสแตนซ์ที่ซ้ำกันลดค่าใช้จ่ายของระบบในการสร้างอินสแตนซ์และบันทึกหน่วยความจำ
มีข้อกำหนดสามประการสำหรับโหมดซิงเกิลตัน:
2. ความแตกต่างระหว่างรูปแบบซิงเกิลและคลาสคงที่
ก่อนอื่นให้เข้าใจว่าคลาสคงที่คืออะไร คลาสคงที่หมายความว่าคลาสมีวิธีการคงที่และฟิลด์คงที่ ตัวสร้างถูกแก้ไขโดยส่วนตัวดังนั้นจึงไม่สามารถสร้างอินสแตนซ์ได้ คลาสคณิตศาสตร์เป็นคลาสคงที่
หลังจากรู้ว่าชั้นเรียนคงที่คืออะไรมาพูดคุยเกี่ยวกับความแตกต่างระหว่างพวกเขา:
1) ก่อนอื่นรูปแบบ Singleton จะช่วยให้คุณมีวัตถุที่เป็นเอกลักษณ์ทั่วโลก คลาสคงที่ให้วิธีการคงที่มากมายแก่คุณ วิธีการเหล่านี้ไม่จำเป็นต้องสร้างและสามารถเรียกได้โดยตรงผ่านชั้นเรียน
2) รูปแบบซิงเกิลมีความยืดหยุ่นสูงกว่าและวิธีการสามารถแทนที่ได้เนื่องจากคลาสคงที่เป็นวิธีการคงที่ทั้งหมดดังนั้นพวกเขาจึงไม่สามารถแทนที่ได้
3) ถ้ามันเป็นวัตถุที่หนักมากรูปแบบซิงเกิลตันอาจขี้เกียจโหลด แต่คลาสคงที่ไม่สามารถทำได้
จากนั้นควรใช้คลาสคงที่และเมื่อใดที่เราควรใช้โหมด Singleton? ก่อนอื่นถ้าคุณต้องการใช้วิธีการเครื่องมือบางอย่างควรใช้คลาสคงที่ การเปรียบเทียบแบบสแตติกนั้นเร็วกว่าคลาสซิงเกิลตัน หากคุณต้องการรักษาข้อมูลสถานะหรือแหล่งข้อมูลการเข้าถึงคุณควรใช้โหมด Singleton นอกจากนี้ยังอาจกล่าวได้ว่าเมื่อคุณต้องการความสามารถเชิงวัตถุ (เช่นการสืบทอด, polymorphism) เลือกคลาสซิงเกิลตันและเมื่อคุณให้วิธีการบางอย่างให้เลือกคลาสคงที่
3. วิธีการใช้โหมดซิงเกิลตัน
1. โหมด Man Hungry
โหมดหิวโหยที่เรียกว่าจะโหลดทันที โดยทั่วไปแล้วอินสแตนซ์ได้รับการสร้างขึ้นก่อนที่จะเรียกวิธีการ getInstancef ซึ่งหมายความว่ามันถูกสร้างขึ้นเมื่อโหลดคลาส ข้อเสียของโมเดลนี้ชัดเจนมากซึ่งก็คือมันใช้ทรัพยากร เมื่อคลาส Singleton มีขนาดใหญ่เราต้องการใช้มันแล้วสร้างอินสแตนซ์ ดังนั้นวิธีนี้จึงเหมาะสำหรับชั้นเรียนที่ใช้ทรัพยากรน้อยลงและจะใช้ในระหว่างการเริ่มต้น
Class Singletonhungary {ส่วนตัว Singletonhungary Singletonhungary = New Singletonhungary (); // ตั้งค่าตัวสร้างเป็นส่วนตัวเพื่อห้ามการสร้างอินสแตนซ์ผ่าน Singletonhungary ส่วนตัวใหม่ () {} สาธารณะคงที่ Singletonhungary Getinstance () {return Singletonhungary; -2. โหมดขี้เกียจ
โหมดขี้เกียจคือการโหลดขี้เกียจหรือที่เรียกว่าการโหลดขี้เกียจ สร้างอินสแตนซ์เมื่อต้องใช้โปรแกรมเพื่อไม่ให้หน่วยความจำเสีย สำหรับโหมดขี้เกียจนี่คือ 5 วิธีการใช้งาน วิธีการใช้งานบางวิธีเป็นความปลอดภัยของเธรดซึ่งหมายความว่าปัญหาการซิงโครไนซ์ทรัพยากรอาจเกิดขึ้นในสภาพแวดล้อมพร้อมกันหลายเธรด
ก่อนอื่นวิธีแรกคือไม่มีปัญหาในเธรดเดี่ยว แต่จะมีปัญหาในการทำมัลติเธรด
// การใช้งานขี้เกียจของโหมดซิงเกิลตัน 1-Thread ชั้นเรียนที่ไม่ปลอดภัย SingletonLazy1 {ส่วนตัวคงที่ SingletonLazy1 Singletonlazy; Private SingletonLazy1 () {} สาธารณะคงที่ SingletonLazy1 GetInstance () {ถ้า (null == SingletonLazy) {ลอง {// จำลองการเตรียมการบางอย่างก่อนที่จะสร้าง thread.sleep (1,000); } catch (interruptedException e) {e.printStackTrace (); } singletonlazy = ใหม่ singletonlazy1 (); } return singletonlazy; -มาจำลอง 10 เธรดอะซิงโครนัสเพื่อทดสอบ:
คลาสสาธารณะ SingletonLazyTest {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {thread2 [] threadarr = thread2 ใหม่ [10]; สำหรับ (int i = 0; i <threadarr.length; i ++) {threadarr [i] = new Thread2 (); Threadarr [i] .start (); }}} // ทดสอบเธรดเธรด 2 ขยายเธรด {@Override โมฆะสาธารณะเรียกใช้ () {System.out.println (SingletonLazy1.getInstance (). hashCode ()); -ผลการทำงาน:
124191239
124191239
872096466
1603289047
1698032342
2456667618
371739364
124191239
1723650563
367137303
คุณจะเห็นว่า hashcodes ของพวกเขาไม่เหมือนกันทั้งหมดซึ่งหมายความว่าวัตถุหลายชิ้นถูกสร้างขึ้นในสภาพแวดล้อมแบบมัลติเธรดซึ่งไม่เป็นไปตามข้อกำหนดของรูปแบบ Singleton
แล้วจะทำให้เธรดปลอดภัยได้อย่างไร? ในวิธีที่สองเราใช้คำหลักที่ซิงโครไนซ์เพื่อซิงโครไนซ์วิธี getInstance
// โหมด Singleton Lazy Adplementation 2-Thread Safety // โดยการตั้งค่าวิธีการซิงโครไนซ์ประสิทธิภาพต่ำเกินไปวิธีทั้งหมดถูกล็อคคลาส SingletonLazy2 {ส่วนตัว Singletonlazy2 Singletonlazy; Private SingletonLazy2 () {} สาธารณะแบบคงที่แบบคงที่ซิงโครไนซ์ SingletonLazy2 getInstance () {ลอง {ถ้า (null == SingletonLazy) {// จำลองการเตรียมการบางอย่างก่อนที่จะสร้าง thread.sleep (1,000); singletonlazy = ใหม่ singletonlazy2 (); }} catch (interruptedException e) {e.printStackTrace (); } return singletonlazy; -การใช้คลาสทดสอบข้างต้นผลการทดสอบ:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
สามารถเห็นได้ว่าวิธีนี้จะได้รับความปลอดภัยจากด้าย อย่างไรก็ตามข้อเสียคือประสิทธิภาพต่ำเกินไปและทำงานร่วมกัน หากเธรดถัดไปต้องการรับวัตถุจะต้องรอให้เธรดก่อนหน้านี้เปิดตัวก่อนที่จะดำเนินการต่อไป
จากนั้นเราไม่สามารถล็อควิธีการ แต่ล็อครหัสภายในซึ่งสามารถบรรลุความปลอดภัยของด้าย แต่วิธีนี้เหมือนกับวิธีการซิงโครไนซ์และยังทำงานร่วมกันและมีประสิทธิภาพต่ำมาก
// การใช้งาน Singletonlazy 3-Thread Safety // โดยการตั้งค่าบล็อกรหัสแบบซิงโครนัสประสิทธิภาพต่ำเกินไปและบล็อกทั้งหมดถูกล็อคคลาส SingletonLazy3 {ส่วนตัว Singletonlazy3 Singletonlazy; Private SingletonLazy3 () {} สาธารณะคงที่ singletonlazy3 getInstance () {ลอง {ซิงโครไนซ์ (singletonlazy3.class) {ถ้า (null == singletonlazy) {// จำลองเพื่อเตรียมการก่อนที่จะสร้างด้ายวัตถุ singletonlazy = ใหม่ singletonlazy3 (); }}} catch (interruptedException e) {// todo: จัดการข้อยกเว้น} ส่งคืน SingletonLazy; -มาปรับรหัสต่อไป เราล็อครหัสที่สร้างวัตถุเท่านั้น แต่สิ่งนี้จะช่วยให้มั่นใจได้ว่ามีความปลอดภัยของเธรดหรือไม่?
// การใช้งานขี้เกียจของโหมดซิงเกิลตัน 4-Thread unsafe // เฉพาะรหัสที่สร้างอินสแตนซ์จะถูกซิงโครไนซ์โดยการตั้งค่าบล็อกรหัสการซิงโครไนซ์ // แต่ยังมีปัญหาด้านความปลอดภัยในระดับ SingletonLazy4 Private SingletonLazy4 () {} สาธารณะคงที่ SingletonLazy4 GetInstance () {ลอง {ถ้า (null == singletonlazy) {// รหัส 1 // จำลองเพื่อเตรียมการก่อนที่จะสร้าง thread.sleep (1,000); ซิงโครไนซ์ (singletonlazy4.class) {singletonlazy = ใหม่ singletonlazy4 (); // รหัส 2}}} catch (interruptedException e) {// todo: จัดการข้อยกเว้น} ส่งคืน SingletonLazy; -มาดูผลการทำงาน:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
ตัดสินจากผลลัพธ์วิธีนี้ไม่สามารถรับประกันความปลอดภัยของเธรดได้ ทำไม สมมติว่าสองเธรด A และ B ไปที่ 'รหัส 1' ในเวลาเดียวกันเพราะวัตถุยังคงว่างเปล่าในเวลานี้ดังนั้นทั้งคู่จึงสามารถป้อนวิธีการได้ ด้ายแรกคว้าล็อคและสร้างวัตถุ หลังจากเธรด B ได้รับการล็อคมันจะไปที่ 'รหัส 2' เมื่อมันถูกปล่อยออกมาและวัตถุถูกสร้างขึ้น ดังนั้นจึงไม่สามารถรับประกันได้ในสภาพแวดล้อมแบบมัลติเธรด
มาปรับให้เหมาะสมต่อไป เนื่องจากมีปัญหาเกี่ยวกับวิธีการข้างต้นเราจึงสามารถตัดสินโมฆะในบล็อกรหัสการซิงโครไนซ์ วิธีนี้เป็นกลไกการล็อคการตรวจสอบ DCL DCL ของเรา
// Slazy Man ในโหมด Singleton ใช้ความปลอดภัย 5-Thread // โดยการตั้งค่าบล็อกรหัสการซิงโครไนซ์ให้ใช้กลไกการล็อค DCL Double Douch Lock // โดยใช้กลไกการล็อคการตรวจสอบสองครั้งที่ประสบความสำเร็จในการแก้ปัญหาความไม่มั่นคงของเธรด เมื่อวัตถุ SingletonLazy5 ถูกสร้างขึ้นเมื่อได้รับวัตถุ SingletonLazy5 ไม่จำเป็นต้องตรวจสอบการล็อคของบล็อกรหัสการซิงโครไนซ์และรหัสที่ตามมาและส่งคืนวัตถุ SingletonLazy5 โดยตรง // ฟังก์ชั่นที่สอง Class SingletonLazy5 {ส่วนตัวคงที่แบบคงที่ Singletonlazy5 Singletonlazy; Private SingletonLazy5 () {} สาธารณะคงที่ SingletonLazy5 GetInstance () {ลอง {ถ้า (null == singletonlazy) {// จำลองการเตรียมการบางอย่างก่อนที่จะสร้าง thread.sleep (1,000); ซิงโครไนซ์ (singletonlazy5.class) {ถ้า (null == singletonlazy) {singletonlazy = ใหม่ singletonlazy5 (); }}}} catch (interruptedException e) {} return singletonlazy; -ผลการทำงาน:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
เราจะเห็นได้ว่ากลไกการล็อค DCL สองครั้งจะช่วยแก้ปัญหาประสิทธิภาพและความปลอดภัยของด้ายของโหมด Singleton ที่กำลังโหลดขี้เกียจ นี่เป็นวิธีที่เราใช้บ่อยที่สุด
คำหลักที่ผันผวน
ที่นี่ฉันสังเกตเห็นว่าเมื่อกำหนด singletonlazy จะใช้คำหลักที่ผันผวน นี่คือการป้องกันคำแนะนำจากการจัดลำดับใหม่ ทำไมเราต้องทำสิ่งนี้? มาดูสถานการณ์:
รหัสไปที่ singletonlazy = ใหม่ singletonlazy5 (); ดูเหมือนว่าจะเป็นประโยค แต่นี่ไม่ใช่การดำเนินการอะตอม (ไม่ว่าจะดำเนินการทั้งหมดหรือทั้งหมดไม่ได้ดำเนินการและครึ่งหนึ่งไม่สามารถดำเนินการได้) ประโยคนี้รวบรวมเป็น 8 คำแนะนำการประกอบและประมาณ 3 สิ่งที่ทำ:
1. จัดสรรหน่วยความจำให้กับอินสแตนซ์ SingletonLazy5
2. เริ่มต้นคอนสตรัคเตอร์ของ SingletonLazy5
3. ชี้วัตถุ Singletonlazy ไปยังพื้นที่หน่วยความจำที่จัดสรร (โปรดทราบว่าอินสแตนซ์นี้ไม่ได้เป็นโมฆะ)
เนื่องจากคอมไพเลอร์ Java อนุญาตให้โปรเซสเซอร์ดำเนินการตามลำดับนอก (สั่งซื้อ) และลำดับของแคชลงทะเบียนกับหน่วยความจำหลักในการเขียนกลับใน JMM (Java Memory Medel) ก่อน JDK1.5 คำสั่งของจุดที่สองและสามด้านบนไม่สามารถรับประกันได้ กล่าวคือคำสั่งการดำเนินการอาจเป็น 1-2-3 หรือ 1-3-2 หากเป็นหลังและก่อนที่ 3 จะถูกดำเนินการและ 2 ไม่ถูกดำเนินการจะเปลี่ยนเป็นเธรด 2 ในเวลานี้ Singletonlazy ได้ดำเนินการจุดที่สามในเธรดหนึ่งแล้ว Singletonlazy นั้นไม่ว่างเปล่าอยู่แล้ว ยิ่งกว่านั้นข้อผิดพลาดประเภทนี้ที่ยากต่อการติดตามและยากที่จะทำซ้ำอาจไม่พบในสัปดาห์สุดท้ายของการดีบัก
วิธีการเขียนของ DCL ที่จะใช้ Singletons แนะนำในหนังสือและตำราเรียนหลายเล่ม (รวมถึงหนังสือที่ใช้ JDK1.4 เวอร์ชันก่อนหน้า) แต่จริงๆแล้วมันไม่ถูกต้องอย่างสมบูรณ์ อันที่จริง DCL เป็นไปได้ในบางภาษา (เช่น C) ขึ้นอยู่กับว่าสามารถรับประกันลำดับ 2 และ 3 ขั้นตอนได้หรือไม่ หลังจาก JDK1.5 เจ้าหน้าที่ได้สังเกตเห็นปัญหานี้ดังนั้น JMM ได้รับการปรับและคำหลักที่ผันผวนได้รับการรับรอง ดังนั้นหาก JDK เป็นรุ่น 1.5 หรือใหม่กว่าคุณจะต้องเพิ่มคำหลักที่ผันผวนลงในคำจำกัดความของ Singletonlazy ซึ่งสามารถมั่นใจได้ว่า Singletonlazy จะถูกอ่านจากหน่วยความจำหลักทุกครั้ง แน่นอนความผันผวนจะส่งผลกระทบต่อประสิทธิภาพมากหรือน้อย สิ่งที่สำคัญที่สุดคือเราต้องพิจารณา JDK1.42 และเวอร์ชันก่อนหน้าดังนั้นการปรับปรุงการเขียนรูปแบบซิงเกิลยังคงดำเนินต่อไป
3. ชั้นเรียนภายในแบบคงที่
จากข้อควรพิจารณาข้างต้นเราสามารถใช้คลาสชั้นในแบบคงที่เพื่อใช้รูปแบบ Singleton รหัสมีดังนี้:
// ใช้โหมด Singleton ด้วยคลาส Inner Inner-Thread Safety Class SingletonStaticinner {ส่วนตัว Singletonstaticinner () {} คลาสคงที่คลาสคงที่ Singletoninner {ส่วนตัว singletonstaticinner singletonstaticinner = new Singletonstaticinner (); } สาธารณะ singletonstaticinner getInstance () {ลอง {thread.sleep (1,000); } catch (interruptedException e) {// toDo บล็อก catch block ที่สร้างขึ้นอัตโนมัติ E.PrintStackTrace (); } return singletoninner.singletonstaticinner; -จะเห็นได้ว่าเราไม่ได้ดำเนินการซิงโครไนซ์ใด ๆ อย่างชัดเจนในลักษณะนี้ดังนั้นมันจะมั่นใจได้อย่างไรว่าจะมั่นใจในความปลอดภัยของเธรดได้อย่างไร เช่นเดียวกับโหมด Hungry Man มันเป็นคุณสมบัติที่ JVM มั่นใจได้ว่าสมาชิกคงที่ของชั้นเรียนสามารถโหลดได้เพียงครั้งเดียวเพื่อให้มีวัตถุอินสแตนซ์เดียวจากระดับ JVM ดังนั้นคำถามคืออะไรคือความแตกต่างระหว่างวิธีนี้กับโมเดล Hungry Man คืออะไร? มันไม่โหลดทันที? ในความเป็นจริงเมื่อมีการโหลดคลาสคลาสภายในของมันจะไม่ถูกโหลดในเวลาเดียวกัน มีการโหลดคลาสซึ่งเกิดขึ้นเมื่อใดและเฉพาะในกรณีที่หนึ่งในสมาชิกคงที่ (โดเมนคงที่ตัวสร้างวิธีการคงที่ ฯลฯ ) เรียกว่า
อาจกล่าวได้ว่าวิธีนี้เป็นทางออกที่ดีที่สุดในการใช้รูปแบบ Singleton
4. บล็อกรหัสคงที่
นี่คือรูปแบบการติดตั้งบล็อกรหัสแบบคงที่ วิธีนี้คล้ายกับวิธีแรกและมันก็เป็นโมเดลที่หิวโหย
// ใช้บล็อกรหัสแบบคงที่เพื่อใช้โหมดโหมดซิงเกิล SingletonStaticBlock {ส่วนตัว SingletonStaticBlock SingletonStaticBlock; Static {SingletonStaticBlock = ใหม่ SingletonStaticBlock (); } สาธารณะ singletonstaticblock getInstance () {return singletonstaticblock; -5. การทำให้เป็นอนุกรมและ deserialization
เหตุใด LZ จึงแนะนำการทำให้เป็นอนุกรมและ deserialization เนื่องจากถึงแม้ว่าโหมด Singleton สามารถมั่นใจได้ถึงความปลอดภัยของเธรด เรียกใช้คลาสทดสอบต่อไปนี้
ระดับสาธารณะ singletonstaticinnerialializetest {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ลอง {singletonstaticinnerserialize serialize = singletonstaticinnerialize.getinstance (); System.out.println (serialize.hashCode ()); // serialize fileOutputStream fo = ใหม่ fileOutputStream ("tem"); ObjectOutputStream OO = ใหม่ ObjectOutputStream (FO); oo.writeObject (serialize); oo.close (); fo.close (); // deserialize fileInputStream fi = ใหม่ fileInputStream ("tem"); ObjectInputStream OI = ใหม่ ObjectInputStream (FI); SingletonStaticInserialize serialize2 = (singletonstaticinnerialize) oi.readobject (); oi.close (); fi.close (); System.out.println (serialize2.hashCode ()); } catch (exception e) {e.printstacktrace (); }}} // ใช้คลาสภายในที่ไม่ระบุชื่อเพื่อใช้รูปแบบ Singleton เมื่อพบกับการทำให้เป็นอนุกรมและ deserialization อินสแตนซ์เดียวกันจะไม่ได้รับ //solve ปัญหานี้คือการใช้วิธี readResolve ในระหว่างการทำให้เป็นอนุกรมนั่นคือลบส่วนหนึ่งของความคิดเห็น Class SingletonstaticInnerialize ใช้งาน Serializable { / *** 28 มีนาคม 2018* / ส่วนตัวคงที่สุดท้าย Long SerialVersionUid = 1L; ชั้นเรียนคงที่ส่วนตัว innerclass {ส่วนตัว singletonstaticinnerialize singletonstaticinnerialize = ใหม่ singletonstaticinnerialize (); } สาธารณะ singletonstaticinnerserialize getInstance () {return innerclass.singletonstaticinnerialialize; } // วัตถุที่ได้รับการป้องกัน readResOlve () {// System.out.println ("วิธีการอ่าน readResolve ถูกเรียกว่า"); // return innerclass.singletonstaticinnerialize; //}}}คุณสามารถเห็น:
865113938
1078694789
ผลลัพธ์แสดงให้เห็นว่ามันเป็นอินสแตนซ์วัตถุที่แตกต่างกันสองรายการที่ละเมิดรูปแบบซิงเกิล แล้วจะแก้ปัญหานี้ได้อย่างไร? วิธีแก้ปัญหาคือการใช้วิธี readresolve () ใน deserialization ลบรหัสความคิดเห็นด้านบนและเรียกใช้อีกครั้ง:
865113938
วิธี readresolve ถูกเรียก
865113938
คำถามคือใครคือวิธีศักดิ์สิทธิ์ของวิธี readresolve ()? ในความเป็นจริงเมื่อ JVM deserializes และ "ประกอบ" วัตถุใหม่จากหน่วยความจำมันจะเรียกใช้วิธี readresolve นี้โดยอัตโนมัติเพื่อส่งคืนวัตถุที่เราระบุไว้และรับประกันกฎ Singleton การเกิดขึ้นของ readResolve () ช่วยให้โปรแกรมเมอร์สามารถควบคุมวัตถุที่ได้รับผ่าน deserialization ด้วยตนเอง
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น