กรอบหน่วยความจำใช้ใน Java อะไร


18

ขณะที่พยายามที่จะเข้าใจว่าSubmissionPublisher( รหัสแหล่งที่มาใน Java SE 10 OpenJDK | เอกสาร ), คลาสใหม่เพิ่มเข้ามาใน Java SE ในรุ่นที่ 9 ได้รับการดำเนินฉัน stumbled ข้าม API โทรไม่กี่คนที่VarHandleฉันไม่ได้ก่อนหน้านี้ตระหนักถึง:

fullFence, acquireFence, releaseFence, และloadLoadFencestoreStoreFence

หลังจากทำวิจัยโดยเฉพาะอย่างยิ่งเกี่ยวกับแนวคิดของอุปสรรคหน่วยความจำ / รั้ว (ฉันเคยได้ยินพวกเขาก่อนหน้านี้ใช่ แต่ไม่เคยใช้พวกเขาจึงค่อนข้างไม่คุ้นเคยกับความหมายของพวกเขา) ฉันคิดว่าฉันมีความเข้าใจพื้นฐานของสิ่งที่พวกเขา . อย่างไรก็ตามเนื่องจากคำถามของฉันอาจเกิดขึ้นจากการเข้าใจผิดฉันต้องการให้แน่ใจว่าฉันได้รับมันตั้งแต่แรก:

  1. อุปสรรคหน่วยความจำกำลัง จำกัด ข้อ จำกัด ใหม่เกี่ยวกับการอ่านและการเขียน

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

  3. c ++ สนับสนุนความหลากหลายของปัญหาและอุปสรรคที่หน่วยความจำVarHandleแต่เหล่านี้ไม่ตรงกับผู้ที่ให้บริการโดย แต่บางส่วนของปัญหาและอุปสรรคที่หน่วยความจำที่มีอยู่ในVarHandleให้ผลกระทบการสั่งซื้อที่มีเข้ากันได้จะสอดคล้อง c ++ อุปสรรคหน่วยความจำของพวกเขา

    • #fullFence เข้ากันได้กับ atomic_thread_fence(memory_order_seq_cst)
    • #acquireFence เข้ากันได้กับ atomic_thread_fence(memory_order_acquire)
    • #releaseFence เข้ากันได้กับ atomic_thread_fence(memory_order_release)
    • #loadLoadFenceและ#storeStoreFenceไม่มีส่วนเคาน์เตอร์ C ++ ที่ใช้ร่วมกันได้

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

  1. อุปสรรคหน่วยความจำส่วนใหญ่ยังมีผลกระทบการประสาน สิ่งเหล่านั้นขึ้นอยู่กับชนิดของสิ่งกีดขวางที่ใช้และคำสั่งสิ่งกีดขวางที่เรียกใช้ก่อนหน้านี้ในเธรดอื่น เนื่องจากความหมายทั้งหมดของคำสั่งสิ่งกีดขวางมีเฉพาะฮาร์ดแวร์ฉันจะยึดติดกับสิ่งกีดขวางระดับสูง (C ++) ใน C ++ ตัวอย่างเช่นการเปลี่ยนแปลงที่ทำก่อนที่จะมีการเปิดตัวการเรียนการสอนอุปสรรคจะมองเห็นด้ายรันซื้อคำแนะนำและอุปสรรค

สมมติฐานของฉันถูกต้องหรือไม่ ถ้าเป็นเช่นนั้นคำถามที่ได้คือ:

  1. อุปสรรคหน่วยความจำที่มีอยู่VarHandleทำให้เกิดการประสานหน่วยความจำชนิดใด?

  2. ไม่ว่าจะเป็นสาเหตุของการซิงโครไนซ์หน่วยความจำหรือไม่สิ่งที่ข้อ จำกัด การเรียงลำดับใหม่อาจมีประโยชน์สำหรับใน Java Java Memory Model ให้การรับประกันที่แข็งแกร่งมากเกี่ยวกับการสั่งซื้อเมื่อฟิลด์ระเหยล็อคหรือVarHandleการดำเนินการ#compareAndSetที่เกี่ยวข้อง

ในกรณีที่คุณกำลังมองหาตัวอย่างข้างต้นBufferedSubscriptionคลาสภายในของSubmissionPublisher(แหล่งลิงก์ด้านบน) สร้างรั้วเต็มในบรรทัดที่ 1079 (ฟังก์ชั่นgrowAndAddเนื่องจากเว็บไซต์ที่เชื่อมโยงไม่รองรับตัวระบุส่วนเพียง CTRL + F สำหรับมัน ) อย่างไรก็ตามมันไม่ชัดเจนสำหรับฉันว่ามันมีไว้เพื่ออะไร


1
ฉันพยายามตอบ แต่เพื่อให้ง่ายมากพวกเขามีอยู่เพราะคนต้องการโหมดที่อ่อนแอกว่าที่ Java มี ตามลำดับจากน้อยไปมากสิ่งเหล่านี้จะเป็น: plain -> opaque -> release/acquire -> volatile (sequential consistency).
ยูจีน

คำตอบ:


11

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

สิ่งแรกที่คุณต้องเข้าใจ:

Java Language Specification (JLS) ไม่ได้กล่าวถึงอุปสรรคใด ๆ สำหรับ java นี้จะมีรายละเอียดการใช้งาน: มันทำหน้าที่ในแง่ของการเกิดขึ้นก่อนที่ความหมาย เพื่อให้สามารถระบุที่เหมาะสมเหล่านี้เป็นไปตาม JMM (Java หน่วยความจำรุ่น) JMM จะมีการเปลี่ยนแปลงค่อนข้างมาก

งานนี้อยู่ระหว่างดำเนินการ

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

อีกตัวอย่างคือเมื่อ (ใน java) คุณต้องการให้volatileแฟล็กควบคุมเธรดเพื่อหยุด / เริ่มต้น คุณจะรู้ว่าคลาสสิก:

volatile boolean stop = false; // on thread writes, one thread reads this    

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

ธงปลอดภัย แต่ไม่ผันผวน? ใช่แน่นอน: VarHandle::set/getOpaque.

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

มาดูกันว่าเราจะประสบความสำเร็จใน java ได้อย่างไร ครั้งแรกของทั้งหมดเช่นที่แปลกใหม่สิ่งที่มีอยู่แล้วใน AtomicInteger::lazySetAPI: นี่คือที่ไม่ได้ระบุใน Java หน่วยความจำรุ่นและมีความหมายไม่ชัดเจน ; คนยังใช้มัน (LMAX, afaik หรือสิ่งนี้สำหรับการอ่านเพิ่มเติม ) IMHO AtomicInteger::lazySetคือVarHandle::releaseFence(หรือVarHandle::storeStoreFence)


ลองตอบคำถามว่าทำไมมีคนต้องการสิ่งเหล่านี้บ้าง

JMM นั้นมีสองวิธีในการเข้าถึงฟิลด์: ธรรมดาและระเหย (ซึ่งรับประกันความสอดคล้องตามลำดับ ) วิธีการทั้งหมดเหล่านี้ที่คุณพูดถึงมีเพื่อนำสิ่งที่อยู่ระหว่างสอง - ปลดปล่อย / รับความหมาย ; มีบางกรณีที่ฉันคิดว่าคนต้องการสิ่งนี้จริง

ยิ่งมากขึ้นผ่อนคลายจากการเปิดตัว / ซื้อจะเป็นสีขาวขุ่นซึ่งผมยังคงพยายามที่จะเข้าใจ


ดังนั้นบรรทัดล่าง (ความเข้าใจของคุณค่อนข้างถูกต้อง btw): ถ้าคุณวางแผนที่จะใช้สิ่งนี้ใน java - พวกเขาไม่มีสเปคในขณะนี้ให้คุณทำเอง หากคุณต้องการที่จะเข้าใจพวกเขาโหมดเทียบเท่า C ++ ของพวกเขาเป็นสถานที่ที่จะเริ่มต้น


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

1
ฉันกำลังเขียนอะไรเพิ่มเติมเกี่ยวกับมัน พิจารณาว่าcasเป็นทั้งอ่านและเขียนทำหน้าที่เหมือนกำแพงเต็มและคุณอาจเข้าใจว่าทำไมมันเป็นที่ต้องการผ่อนคลาย เช่นเมื่อใช้การล็อคการกระทำแรกคือ cas (0, 1) บนจำนวนการล็อก แต่คุณต้องการได้รับ semantic (เช่น volatile read) ในขณะที่การเขียนสุดท้ายของ 0 เพื่อปลดล็อคควรจะมี semantic (เช่น volatile write) ) ดังนั้นจึงมีสิ่งที่เกิดขึ้นก่อนระหว่างการปลดล็อคและการล็อคครั้งต่อไป การได้มา / การเปิดตัวอ่อนแอกว่าการอ่าน / เขียนที่ไม่แน่นอนเกี่ยวกับเธรดโดยใช้การล็อกที่แตกต่าง
Holger

1
@Peter Cordes: C รุ่นแรกที่มีvolatileคำหลักคือ C99 ห้าปีหลังจาก Java แต่ก็ยังขาดความหมายที่เป็นประโยชน์แม้ C ++ 03 จะไม่มี Memory Model สิ่งที่ C ++ เรียกว่า "atomic" นั้นอายุน้อยกว่า Java มากเช่นกัน และvolatileคำสำคัญไม่ได้บอกเป็นนัยถึงการปรับปรุงอะตอม เหตุใดจึงควรตั้งชื่อเช่นนี้
Holger

1
@PeterCordes บางทีฉันสับสนด้วยrestrictอย่างไรก็ตามฉันจำครั้งที่ต้องเขียน__volatileเพื่อใช้ส่วนขยายคอมไพเลอร์ที่ไม่ใช่คำหลัก ดังนั้นบางทีมันใช้ C89 ไม่สมบูรณ์ใช่ไหม อย่าบอกฉันว่าฉันแก่แล้ว ก่อนหน้า Java 5 volatileนั้นเข้าใกล้ C มาก แต่ Java ไม่มี MMIO ดังนั้นจุดประสงค์ของมันก็คือการทำเธรดหลายตัว แต่ semantic ของ pre-Java 5 นั้นไม่ค่อยมีประโยชน์ ดังนั้นจึงมีการเพิ่ม / ปลดปล่อยเช่นซีแมนทิกส์ แต่ก็ไม่ใช่อะตอมมิก (การอัพเดตอะตอมมิกเป็นคุณลักษณะเพิ่มเติมที่สร้างขึ้นบน)
Holger

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