สำนวนของคุณจะปลอดภัยถ้าหากว่าการอ้างอิงถึงสิ่งHashMap
นั้นได้รับการตีพิมพ์อย่างปลอดภัยแล้ว แทนที่จะเป็นสิ่งที่เกี่ยวข้องกับHashMap
ตัวตนของตัวเองสิ่งพิมพ์ที่ปลอดภัยเกี่ยวข้องกับวิธีการสร้างเธรดทำให้การอ้างอิงไปยังแผนที่สามารถมองเห็นกระทู้อื่น ๆ ได้
โดยพื้นฐานแล้วการแข่งขันที่เป็นไปได้เพียงอย่างเดียวที่นี่คือระหว่างการสร้างHashMap
และการอ่านเธรดใด ๆ ที่สามารถเข้าถึงได้ก่อนที่มันจะถูกสร้างขึ้นอย่างสมบูรณ์ การอภิปรายส่วนใหญ่เกี่ยวกับสิ่งที่เกิดขึ้นกับสถานะของวัตถุแผนที่ แต่สิ่งนี้ไม่เกี่ยวข้องเนื่องจากคุณไม่เคยแก้ไขมันดังนั้นส่วนที่น่าสนใจเพียงอย่างเดียวก็คือHashMap
การเผยแพร่ข้อมูลอ้างอิง
ตัวอย่างเช่นสมมติว่าคุณเผยแพร่แผนที่ดังนี้:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... และ ณ จุดหนึ่งsetMap()
มีการเรียกใช้แผนที่และมีเธรดอื่น ๆ ที่ใช้SomeClass.MAP
ในการเข้าถึงแผนที่และตรวจสอบ null เช่นนี้:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
สิ่งนี้ไม่ปลอดภัยแม้ว่ามันอาจปรากฏขึ้นราวกับว่ามันเป็น ปัญหาคือว่าไม่มีความสัมพันธ์เกิดขึ้นก่อนระหว่างชุดของSomeObject.MAP
และการอ่านที่ตามมาบนเธรดอื่นดังนั้นเธรดการอ่านมีอิสระที่จะดูแผนที่ที่สร้างขึ้นบางส่วน นี้สวยมากสามารถทำอะไรและแม้กระทั่งในทางปฏิบัติมันไม่สิ่งที่ชอบใส่ด้ายอ่านลงในวง จำกัด
หากต้องการเผยแพร่แผนที่อย่างปลอดภัยคุณต้องสร้างความสัมพันธ์ก่อนเกิดขึ้นระหว่างการเขียนการอ้างอิงถึงHashMap
(เช่นสิ่งพิมพ์ ) และผู้อ่านที่ตามมาของการอ้างอิงนั้น (เช่นปริมาณการใช้) สะดวกมีเพียงไม่กี่วิธีที่ง่ายต่อการจดจำประสบความสำเร็จที่[1] :
- แลกเปลี่ยนการอ้างอิงผ่านช่องที่ล็อคอย่างเหมาะสม ( JLS 17.4.5 )
- ใช้ initializer คงที่เพื่อทำการเริ่มต้นร้านค้า ( JLS 12.4 )
- แลกเปลี่ยนการอ้างอิงผ่านสนามระเหย ( JLS 17.4.5 ) หรือเป็นผลมาจากกฎนี้ผ่านคลาส AtomicX
- เริ่มต้นค่าลงในเขตข้อมูลสุดท้าย ( JLS 17.5 )
สิ่งที่น่าสนใจที่สุดสำหรับสถานการณ์ของคุณคือ (2), (3) และ (4) โดยเฉพาะอย่างยิ่ง (3) นำไปใช้โดยตรงกับรหัสที่ฉันมีด้านบน: หากคุณเปลี่ยนการประกาศMAP
เป็น:
public static volatile HashMap<Object, Object> MAP;
ดังนั้นทุกอย่างเป็นเพียว: ผู้อ่านที่เห็นค่าที่ไม่เป็นศูนย์จำเป็นต้องมีความสัมพันธ์ที่เกิดขึ้นก่อนกับร้านค้าMAP
และด้วยเหตุนี้จึงเห็นร้านค้าทั้งหมดที่เกี่ยวข้องกับการเริ่มต้นแผนที่
วิธีอื่นเปลี่ยนความหมายของวิธีการของคุณเนื่องจากทั้ง (2) (โดยใช้ static initalizer) และ (4) (ใช้ขั้นสุดท้าย ) หมายความว่าคุณไม่สามารถตั้งค่าMAP
แบบไดนามิกที่รันไทม์ หากคุณไม่ต้องการทำเช่นนั้นเพียงแค่ประกาศMAP
ว่าเป็นstatic final HashMap<>
และคุณได้รับการรับรองสิ่งพิมพ์ที่ปลอดภัย
ในทางปฏิบัติกฎนั้นง่ายสำหรับการเข้าถึง "วัตถุที่ไม่เคยแก้ไข" อย่างปลอดภัย:
หากคุณกำลังเผยแพร่วัตถุที่ไม่เปลี่ยนแปลงไม่ได้โดยเนื้อแท้ (เช่นในทุกสาขาที่ประกาศfinal
) และ:
- คุณสามารถสร้างวัตถุที่จะถูกกำหนดในขณะที่มีการประกาศa : เพียงแค่ใช้
final
ฟิลด์ (รวมถึงstatic final
สมาชิกแบบคงที่)
- คุณต้องการที่จะกำหนดวัตถุที่ต่อมาหลังจากการอ้างอิงที่มีอยู่แล้วสามารถมองเห็นได้: ใช้สารระเหยฟิลด์ข
แค่นั้นแหละ!
ในทางปฏิบัติมันมีประสิทธิภาพมาก static final
ตัวอย่างเช่นการใช้ฟิลด์ช่วยให้ JVM ถือว่าค่าไม่เปลี่ยนแปลงสำหรับอายุการใช้งานของโปรแกรมและปรับให้เหมาะสมอย่างมาก การใช้final
เขตข้อมูลสมาชิกช่วยให้สถาปัตยกรรมส่วนใหญ่สามารถอ่านฟิลด์ในลักษณะที่เทียบเท่ากับฟิลด์ปกติที่อ่านและไม่ยับยั้งการปรับให้เหมาะสมต่อไปcค
ในที่สุดการใช้งานvolatile
จะมีผลกระทบบางอย่าง: ไม่จำเป็นต้องใช้ฮาร์ดแวร์กั้นในสถาปัตยกรรมจำนวนมาก (เช่น x86 โดยเฉพาะที่ไม่อนุญาตให้อ่านเพื่อส่งผ่านการอ่าน) แต่การเพิ่มประสิทธิภาพและการเรียงลำดับใหม่อาจไม่เกิดขึ้นในเวลารวบรวม ผลกระทบโดยทั่วไปมีขนาดเล็ก ในการแลกเปลี่ยนคุณจะได้รับมากกว่าที่คุณขอไม่เพียง แต่คุณจะสามารถเผยแพร่ได้อย่างปลอดภัยHashMap
คุณสามารถจัดเก็บจำนวนมากที่ไม่ได้แก้ไขHashMap
ตามที่คุณต้องการอ้างอิงเดียวกันและมั่นใจได้ว่าผู้อ่านทุกคนจะเห็นแผนที่ที่เผยแพร่อย่างปลอดภัย .
สำหรับรายละเอียดเต็มไปด้วยเลือดเพิ่มเติมโปรดดูที่Shipilevหรือคำถามที่พบบ่อยนี้โดยแมนสันและเก๊
[1] โดยตรงข้อความจากshipilev
aนั่นฟังดูซับซ้อน แต่สิ่งที่ฉันหมายถึงคือคุณสามารถกำหนดการอ้างอิงในเวลาก่อสร้างได้ไม่ว่าจะที่จุดประกาศหรือในตัวสร้าง (เขตข้อมูลสมาชิก) หรือตัวย่อคงที่ (แบบคงที่)
bหรือคุณสามารถใช้synchronized
วิธีรับ / ตั้งค่าAtomicReference
หรืออะไรก็ได้ แต่เรากำลังพูดถึงงานขั้นต่ำที่คุณสามารถทำได้
c สถาปัตยกรรมบางอย่างที่มีโมเดลหน่วยความจำอ่อนแอมาก (ฉันกำลังมองคุณอัลฟ่า) อาจต้องการกำแพงกั้นการอ่านบางประเภทก่อนfinal
อ่าน - แต่สิ่งเหล่านี้หายากมากในวันนี้