เหตุใด ConcurrentHashMap จึงป้องกันคีย์และค่าว่าง


141

JavaDoc ของConcurrentHashMapพูดว่า:

เหมือนHashtableแต่แตกต่างจากHashMapชั้นนี้ไม่ได้ช่วยให้nullเพื่อนำมาใช้เป็นคีย์หรือค่า

คำถามของฉัน: ทำไม

คำถามที่สอง: ทำไมไม่Hashtableอนุญาตให้เป็นโมฆะ

ฉันใช้ HashMaps จำนวนมากเพื่อจัดเก็บข้อมูล แต่เมื่อเปลี่ยนเป็นConcurrentHashMapฉันมีปัญหาหลายครั้งเพราะ NullPointerExceptions


1
ฉันคิดว่ามันช่างน่ารำคาญอย่างมาก EnumMap ไม่อนุญาตให้มีค่าว่างเช่นกัน เห็นได้ชัดว่าไม่มีข้อ จำกัด ทางเทคนิคที่ไม่อนุญาตให้มีปุ่มว่าง สำหรับ Map <K, V> เพียงแค่ฟิลด์ V-typed จะให้การสนับสนุนสำหรับคีย์ null (อาจเป็นฟิลด์บูลีนอื่นหากคุณต้องการแยกความแตกต่างระหว่างค่า null และไม่มีค่า)
RAY

6
คำถามที่ดีกว่าคือ "ทำไม HashMap จึงอนุญาตให้ใช้ค่าคีย์และค่าว่างได้?" หรืออาจเป็นเพราะเหตุใด Java จึงอนุญาตให้มีค่า Null สำหรับทุกประเภท?
Jed Wesley-Smith

คำตอบ:


220

จากผู้เขียนของConcurrentHashMapตัวเอง (ดั๊กทุ่งหญ้า) :

สาเหตุหลักที่ไม่อนุญาตให้มีค่า Null ใน ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) คือความคลุมเครือที่อาจทนได้เพียงเล็กน้อยในแผนที่ที่ไม่เกิดขึ้นพร้อมกันนั้นไม่สามารถทำได้ หลักสำคัญคือถ้าmap.get(key)ส่งคืนnullคุณไม่สามารถตรวจพบว่าคีย์แมปกับnullvs คีย์นั้นไม่ถูกแมปอย่างชัดเจนหรือไม่ ในแผนที่ที่ไม่เกิดขึ้นพร้อมกันคุณสามารถตรวจสอบสิ่งนี้ได้ผ่านทาง map.contains(key)แต่ในแผนที่ที่เกิดขึ้นพร้อมกันแผนที่อาจมีการเปลี่ยนแปลงระหว่างการโทร


7
ขอบคุณ แต่สิ่งที่เกี่ยวกับการมีโมฆะเป็นกุญแจสำคัญ?
AmitW

2
ทำไมไม่ใช้Optionals เป็นค่าภายใน
benez

2
@benez Optionalเป็นคุณลักษณะ Java 8 ซึ่งไม่สามารถใช้งานได้ในตอนนี้ (Java 5) คุณสามารถใช้Optionals ตอนนี้แน่นอน
บรูโน่

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

44

ฉันเชื่อว่าอย่างน้อยก็บางส่วนเพื่อให้คุณสามารถรวมcontainsKeyและgetเป็นสายเดียว หากแผนที่สามารถเก็บgetค่า Null ได้จะไม่มีวิธีบอกได้ว่าคืนค่า Null เนื่องจากไม่มีรหัสสำหรับค่านั้นหรือเพียงเพราะค่าเป็น Null

ทำไมเป็นปัญหา เพราะไม่มีวิธีที่ปลอดภัยที่จะทำด้วยตัวเอง ใช้รหัสต่อไปนี้:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

เนื่องจากmเป็นแผนที่ที่เกิดขึ้นพร้อมกันคีย์ k อาจถูกลบระหว่างcontainsKeyและการgetโทรทำให้ตัวอย่างข้อมูลนี้ส่งคืนค่า null ที่ไม่เคยอยู่ในตารางแทนที่จะเป็นที่ต้องการKeyNotPresentExceptionที่ไม่เคยอยู่ในตารางมากกว่าที่ต้องการ

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


map.getOrDefault(key, NULL_MARKER)คุณสามารถทำได้ ถ้ามันมีค่าเป็นnull nullถ้ามันส่งคืนNULL_MARKERค่าที่ไม่ได้อยู่
Oliv

@Oliv เฉพาะใน Java 8 นอกจากนี้อาจไม่มีตัวทำเครื่องหมาย null ที่สมเหตุสมผลสำหรับประเภทนั้น
Alice Purcell

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

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

@AlicePurcell ยอดเยี่ยม แม้ว่าจะเป็นไปได้ในทางเทคนิค แต่แน่นอนว่ามันจะเป็นฝันร้ายของการบำรุงรักษาและผู้ใช้ในภายหลังจะไม่ถูกคาดหวังว่าจะต้องซิงค์ในเวอร์ชันที่เกิดขึ้นพร้อมกัน
samshers

4

Josh Bloch ออกแบบมาHashMap; Doug Lea ออกแบบConcurrentHashMapแล้ว ฉันหวังว่านั่นไม่ใช่การหมิ่นประมาท ที่จริงฉันคิดว่าปัญหาคือ nulls มักจะต้องมีการตัดเพื่อให้ null จริงสามารถทนต่อการเริ่มต้น หากรหัสลูกค้าต้องมีค่า Null ก็สามารถชำระค่าห่อ Null ได้เอง


2

คุณไม่สามารถซิงโครไนซ์กับค่า null

แก้ไข: นี่ไม่ใช่เหตุผลในกรณีนี้ ตอนแรกฉันคิดว่ามีบางสิ่งบางอย่างแฟนซีเกิดขึ้นกับการล็อคสิ่งต่าง ๆ กับการปรับปรุงที่เกิดขึ้นพร้อมกันหรือใช้การตรวจสอบวัตถุเพื่อตรวจสอบว่ามีอะไรบางอย่างถูกปรับเปลี่ยน แต่เมื่อตรวจสอบซอร์สโค้ดก็ปรากฏว่าฉันผิด bitmask ของกัญชา

ในกรณีนั้นฉันสงสัยว่าพวกเขาทำมันเพื่อคัดลอก Hashtable และฉันสงสัยว่า Hashtable ทำเพราะในโลกของฐานข้อมูลเชิงสัมพันธ์ null! = null ดังนั้นการใช้ null เป็นคีย์จึงไม่มีความหมาย


ฮะ? ไม่มีการซิงโครไนซ์บนปุ่มและค่าของแผนที่ นั่นจะทำให้ไม่มีเหตุผล
Tobias Müller

มีการล็อคประเภทอื่น ๆ อีกหลายอย่าง นั่นคือสิ่งที่ทำให้มัน "พร้อมกัน" ในการดำเนินการดังกล่าวจำเป็นต้องมี Object เพื่อหยุดทำงาน
Paul Tomblin

2
ทำไมไม่มี speciall Object ภายในที่สามารถใช้สำหรับการซิงโครไนซ์ค่า Null ได้? เช่น "private Object NULL = new Object ();" ฉันคิดว่าฉันเคยเห็นสิ่งนี้มาก่อน ...
Marcel

คุณหมายถึงการล็อคประเภทอื่น ๆ ?
Tobias Müller

ที่จริงแล้วตอนนี้ฉันดูซอร์สโค้ดgee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/ฉันมีข้อสงสัยอย่างมากเกี่ยวกับเรื่องนี้ ปรากฏว่าใช้การล็อคกลุ่มไม่ล็อคแต่ละรายการ
พอลทอมบลิน

0

ConcurrentHashMap ปลอดภัยสำหรับเธรด ฉันเชื่อว่าการไม่อนุญาตให้ใช้คีย์และค่า Null เป็นส่วนหนึ่งของการทำให้แน่ใจว่าปลอดภัยสำหรับเธรด


0

ฉันเดาว่าตัวอย่างของเอกสาร API ต่อไปนี้ให้คำแนะนำที่ดี: "คลาสนี้สามารถใช้งานร่วมกับ Hashtable ได้อย่างสมบูรณ์ในโปรแกรมที่ใช้ความปลอดภัยของเธรด แต่ไม่ใช่ในรายละเอียดการซิงโครไนซ์"

พวกเขาอาจแค่ต้องการให้ConcurrentHashMapเข้ากันได้อย่างเต็มที่ / เปลี่ยนHashtableได้ และHashtableไม่อนุญาตให้ใช้คีย์และค่า Null ..


2
และทำไม Hashtable จึงไม่สนับสนุน null?
Marcel

จากการดูโค้ดฉันไม่เห็นเหตุผลที่ชัดเจนว่าทำไม Hashtable ไม่อนุญาตให้มีค่า Null บางทีมันอาจจะเป็นแค่การตัดสินใจของ API จากด้านหลังเมื่อสร้างคลาสแล้ว! HashMap มีการจัดการเป็นพิเศษสำหรับกรณี null ภายในซึ่ง Hashtable ไม่ (มันมักจะโยน NullPointerException)
Tobias Müller

-2

ฉันไม่คิดว่าการอนุญาตให้มีค่า Null เป็นตัวเลือกที่ถูกต้อง ในหลายกรณีเราต้องการใส่คีย์ที่มีค่า Null เข้าไปในแผนที่ con-current อย่างไรก็ตามโดยใช้ ConcurrentHashMap เราไม่สามารถทำเช่นนั้นได้ ฉันขอแนะนำให้ JDK รุ่นต่อไปสามารถรองรับได้


1
คุณคิดเกี่ยวกับการแข่งขันเพื่อ Javachampion?
BlackBishop

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