JavaRush /จาวาบล็อก /Random-TH /การจัดการความผันผวน
lexmirnov
ระดับ
Москва

การจัดการความผันผวน

เผยแพร่ในกลุ่ม

แนวทางการใช้ตัวแปรระเหย

โดย Brian Goetz 19 มิถุนายน 2550 ต้นฉบับ: การจัดการความ ผันผวน ตัวแปรผันผวนใน Java สามารถเรียกว่า "synchronized-light"; พวกเขาต้องการโค้ดในการใช้งานน้อยกว่าบล็อกที่ซิงโครไนซ์ ซึ่งมักจะทำงานเร็วกว่า แต่สามารถทำได้เพียงเสี้ยวหนึ่งของบล็อกที่ซิงโครไนซ์เท่านั้น บทความนี้นำเสนอรูปแบบต่างๆ สำหรับการใช้สารระเหยอย่างมีประสิทธิภาพ และคำเตือนบางประการเกี่ยวกับจุดที่ไม่ควรใช้ การล็อคมีคุณสมบัติหลักสองประการ: การยกเว้นร่วมกัน (mutex) และการมองเห็น การแยกออกร่วมกันหมายความว่าสามารถล็อคได้ครั้งละหนึ่งเธรดเท่านั้น และคุณสมบัตินี้สามารถใช้เพื่อนำโปรโตคอลการควบคุมการเข้าถึงไปใช้สำหรับทรัพยากรที่ใช้ร่วมกัน เพื่อให้มีเธรดเดียวเท่านั้นที่จะใช้ในแต่ละครั้ง การมองเห็นเป็นปัญหาที่ละเอียดอ่อนกว่า โดยมีวัตถุประสงค์เพื่อให้แน่ใจว่าการเปลี่ยนแปลงที่ทำกับทรัพยากรสาธารณะก่อนที่จะปลดล็อคจะมองเห็นได้ในเธรดถัดไปที่เข้ามาแทนที่การล็อคนั้น หากการซิงโครไนซ์ไม่รับประกันการมองเห็น เธรดอาจได้รับค่าเก่าหรือค่าที่ไม่ถูกต้องสำหรับตัวแปรสาธารณะ ซึ่งอาจนำไปสู่ปัญหาร้ายแรงหลายประการ
ตัวแปรผันผวน
ตัวแปรระเหยมีคุณสมบัติการมองเห็นเหมือนกับตัวแปรที่ซิงโครไนซ์ แต่ไม่มีอะตอมมิกซิตี ซึ่งหมายความว่าเธรดจะใช้ค่าปัจจุบันของตัวแปรผันผวนโดยอัตโนมัติ สามารถใช้เพื่อความปลอดภัยของเธรดแต่ในกรณีที่ จำกัด มาก: กรณีที่ไม่ได้แนะนำความสัมพันธ์ระหว่างตัวแปรหลายตัวหรือระหว่างค่าปัจจุบันและอนาคตของตัวแปร ดังนั้นความผันผวนเพียงอย่างเดียวจึงไม่เพียงพอที่จะใช้ตัวนับ mutex หรือคลาสใดๆ ที่มีส่วนที่ไม่เปลี่ยนรูปเชื่อมโยงกับตัวแปรหลายตัว (เช่น "start <=end") คุณสามารถเลือกล็อคแบบระเหยได้ด้วยเหตุผลสองประการ: ความเรียบง่ายหรือความสามารถในการปรับขนาด โครงสร้างภาษาบางภาษาเขียนได้ง่ายกว่าเป็นโค้ดโปรแกรม และอ่านและทำความเข้าใจในภายหลังได้ เมื่อใช้ตัวแปรระเหยแทนการล็อค นอกจากนี้ ต่างจากล็อคตรงตรงที่ไม่สามารถบล็อกเธรดได้ ดังนั้นจึงมีโอกาสน้อยที่จะเกิดปัญหาเรื่องความสามารถในการขยายขนาด ในสถานการณ์ที่มีการอ่านมากกว่าการเขียน ตัวแปรระเหยสามารถให้ประโยชน์ด้านประสิทธิภาพมากกว่าการล็อก
เงื่อนไขการใช้สารระเหยที่ถูกต้อง
คุณสามารถเปลี่ยนตัวล็อคเป็นแบบระเหยได้ในสถานการณ์จำนวนจำกัด เพื่อให้เธรดปลอดภัย ต้องเป็นไปตามเกณฑ์ทั้งสอง:
  1. สิ่งที่เขียนลงในตัวแปรจะไม่ขึ้นอยู่กับค่าปัจจุบัน
  2. ตัวแปรไม่มีส่วนร่วมในค่าคงที่กับตัวแปรอื่น
พูดง่ายๆ ก็คือ เงื่อนไขเหล่านี้หมายความว่าค่าที่ถูกต้องที่สามารถเขียนลงในตัวแปรระเหยได้นั้นไม่ขึ้นอยู่กับสถานะอื่นของโปรแกรม รวมถึงสถานะปัจจุบันของตัวแปรด้วย เงื่อนไขแรกไม่รวมการใช้ตัวแปรระเหยเป็นตัวนับที่ปลอดภัยสำหรับเธรด แม้ว่าการเพิ่มขึ้น (x++) จะดูเหมือนเป็นการดำเนินการครั้งเดียว แต่แท้จริงแล้วมันเป็นลำดับทั้งหมดของการดำเนินการอ่าน-แก้ไข-เขียนที่ต้องดำเนินการแบบอะตอม ซึ่งความผันผวนไม่ได้ระบุไว้ การดำเนินการที่ถูกต้องจะต้องให้ค่า x คงเดิมตลอดการดำเนินการ ซึ่งไม่สามารถทำได้โดยใช้ค่าระเหย (อย่างไรก็ตาม หากคุณมั่นใจได้ว่าค่าถูกเขียนจากเธรดเดียวเท่านั้น เงื่อนไขแรกก็สามารถละเว้นได้) ในสถานการณ์ส่วนใหญ่ เงื่อนไขแรกหรือเงื่อนไขที่สองจะถูกละเมิด ทำให้ตัวแปรระเหยเป็นวิธีที่ใช้กันทั่วไปน้อยกว่าในการได้รับความปลอดภัยของเธรดมากกว่าตัวแปรที่ซิงโครไนซ์ รายการ 1 แสดงคลาสที่ไม่ปลอดภัยสำหรับเธรดพร้อมช่วงตัวเลข มันมีค่าคงที่ - ขอบล่างจะน้อยกว่าหรือเท่ากับขอบบนเสมอ @NotThreadSafe public class NumberRange { private int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(...); lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(...); upper = value; } } เนื่องจากตัวแปรสถานะช่วงถูกจำกัดด้วยวิธีนี้ จึงไม่เพียงพอที่จะทำให้ฟิลด์ด้านล่างและด้านบนมีความผันผวนเพื่อให้แน่ใจว่าคลาสมีเธรดที่ปลอดภัย ยังคงจำเป็นต้องมีการซิงโครไนซ์ มิฉะนั้นไม่ช้าก็เร็วคุณจะโชคไม่ดีและสองเธรดที่ทำงาน setLower() และ setUpper() ที่มีค่าที่ไม่เหมาะสมอาจทำให้ช่วงมีสถานะไม่สอดคล้องกัน ตัวอย่างเช่น ถ้าค่าเริ่มต้นคือ (0, 5) เธรด A เรียก setLower(4) และในเวลาเดียวกัน เธรด B เรียก setUpper(3) การดำเนินการแทรกเหล่านี้จะส่งผลให้เกิดข้อผิดพลาด แม้ว่าทั้งสองจะผ่านการตรวจสอบ ที่ควรปกป้องค่าคงที่ เป็นผลให้ช่วงจะเป็น (4, 3) - ค่าที่ไม่ถูกต้อง เราจำเป็นต้องสร้าง setLower() และ setUpper() atomic เป็นการดำเนินการช่วงอื่น - และการทำให้ฟิลด์มีความผันผวนจะไม่ทำเช่นนั้น
การพิจารณาประสิทธิภาพ
เหตุผลแรกที่ต้องใช้ความผันผวนคือความเรียบง่าย ในบางสถานการณ์ การใช้ตัวแปรดังกล่าวจะง่ายกว่าการใช้การล็อกที่เกี่ยวข้องกัน เหตุผลที่สองคือประสิทธิภาพ บางครั้งความผันผวนจะทำงานได้เร็วกว่าการล็อค เป็นเรื่องยากมากที่จะสร้างคำสั่งที่ครอบคลุมทุกด้านอย่างแม่นยำ เช่น "X เร็วกว่า Y เสมอ" โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการดำเนินการภายในของ Java Virtual Machine (ตัวอย่างเช่น JVM อาจปลดการล็อกทั้งหมดในบางสถานการณ์ ทำให้เป็นการยากที่จะหารือเกี่ยวกับต้นทุนของการระเหยและการซิงโครไนซ์ในลักษณะเชิงนามธรรม) อย่างไรก็ตาม สำหรับสถาปัตยกรรมโปรเซสเซอร์สมัยใหม่ส่วนใหญ่ ค่าใช้จ่ายในการอ่านค่าความผันผวนไม่แตกต่างจากค่าใช้จ่ายในการอ่านตัวแปรทั่วไปมากนัก ค่าใช้จ่ายในการเขียนแบบระเหยนั้นสูงกว่าการเขียนตัวแปรทั่วไปอย่างมาก เนื่องจากการกั้นหน่วยความจำที่จำเป็นสำหรับการมองเห็น แต่โดยทั่วไปจะมีราคาถูกกว่าการตั้งค่าการล็อค
รูปแบบการใช้สารระเหยอย่างเหมาะสม
ผู้เชี่ยวชาญด้านการทำงานพร้อมกันหลายคนมักจะหลีกเลี่ยงการใช้ตัวแปรที่ระเหยไปพร้อมกัน เนื่องจากใช้อย่างถูกต้องได้ยากกว่าการล็อค อย่างไรก็ตาม มีรูปแบบที่กำหนดไว้อย่างชัดเจนซึ่งหากปฏิบัติตามอย่างระมัดระวัง ก็สามารถใช้ได้อย่างปลอดภัยในสถานการณ์ที่หลากหลาย เคารพข้อจำกัดของความผันผวนเสมอ - ใช้เฉพาะความผันผวนที่ไม่ขึ้นอยู่กับสิ่งอื่นใดในโปรแกรม และสิ่งนี้ควรป้องกันไม่ให้คุณเข้าสู่ดินแดนที่เป็นอันตรายด้วยรูปแบบเหล่านี้
รูปแบบ #1: ธงสถานะ
บางทีการใช้ตัวแปรที่ไม่แน่นอนตามรูปแบบบัญญัติอาจเป็นแฟล็กสถานะบูลีนธรรมดาที่บ่งชี้ว่ามีเหตุการณ์วงจรการใช้งานที่สำคัญเพียงครั้งเดียวเกิดขึ้น เช่น การเริ่มต้นเสร็จสมบูรณ์หรือคำขอปิดระบบ แอปพลิเคชั่นจำนวนมากมีโครงสร้างการควบคุมในรูปแบบ: "จนกว่าเราจะพร้อมที่จะปิดการทำงาน ให้ทำงานต่อไป" ดังที่แสดงในรายการ 2: เป็นไปได้ volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } } ว่าเมธอด shutdown() จะถูกเรียกจากที่ไหนสักแห่งนอกลูป - บนเธรดอื่น - ดังนั้นจึงจำเป็นต้องมีการซิงโครไนซ์เพื่อให้แน่ใจว่ามีการร้องขอการปิดการมองเห็นตัวแปรที่ถูกต้อง (สามารถเรียกได้จาก JMX Listener, Action Listener ในเธรดเหตุการณ์ GUI, ผ่าน RMI, ผ่านบริการเว็บ ฯลฯ) อย่างไรก็ตาม การวนซ้ำที่มีบล็อกที่ซิงโครไนซ์จะยุ่งยากกว่าการวนซ้ำที่มีสถานะผันผวนดังในรายการที่ 2 เนื่องจากการผันผวนทำให้การเขียนโค้ดง่ายขึ้น และการตั้งค่าสถานะไม่ขึ้นอยู่กับสถานะของโปรแกรมอื่น ๆ นี่คือตัวอย่างของ ใช้สารระเหยได้ดี ลักษณะของธงสถานะดังกล่าวคือโดยปกติจะมีการเปลี่ยนสถานะเพียงสถานะเดียวเท่านั้น การตั้งค่าสถานะshutdownRequestedเปลี่ยนจากfalseเป็นจริงและจากนั้นโปรแกรมจะปิดตัวลง รูปแบบนี้สามารถขยายไปยังสถานะสถานะที่สามารถเปลี่ยนไปมาได้ แต่เฉพาะในกรณีที่วงจรการเปลี่ยนแปลง (จากเท็จเป็นจริงเป็นเท็จ) เกิดขึ้นโดยไม่มีการแทรกแซงจากภายนอก ไม่เช่นนั้นกลไกการเปลี่ยนผ่านของอะตอมบางประเภท เช่น ตัวแปรของอะตอม เป็นสิ่งจำเป็น
รูปแบบที่ 2: การเผยแพร่ที่ปลอดภัยเพียงครั้งเดียว
ข้อผิดพลาดในการมองเห็นที่อาจเกิดขึ้นเมื่อไม่มีการซิงโครไนซ์อาจกลายเป็นปัญหาที่ยากยิ่งขึ้นเมื่อเขียนการอ้างอิงออบเจ็กต์แทนค่าดั้งเดิม หากไม่มีการซิงโครไนซ์ คุณสามารถดูค่าปัจจุบันสำหรับการอ้างอิงออบเจ็กต์ที่เขียนโดยเธรดอื่น และยังคงเห็นค่าสถานะเก่าสำหรับออบเจ็กต์นั้น (ภัยคุกคามนี้เป็นต้นตอของปัญหาด้วยการล็อคการตรวจสอบซ้ำซ้อนที่น่าอับอาย โดยที่การอ้างอิงออบเจ็กต์ถูกอ่านโดยไม่มีการซิงโครไนซ์ และคุณเสี่ยงที่จะเห็นการอ้างอิงจริงแต่ได้รับออบเจ็กต์ที่สร้างขึ้นบางส่วนผ่านการอ้างอิงนั้น) วิธีหนึ่งในการเผยแพร่อย่างปลอดภัย object คือการอ้างอิงถึงวัตถุที่ระเหยได้ รายการ 3 แสดงตัวอย่างที่เธรดพื้นหลังโหลดข้อมูลบางอย่างจากฐานข้อมูลในระหว่างการเริ่มต้นระบบ โค้ดอื่นๆ ที่อาจลองใช้ข้อมูลนี้ตรวจสอบว่ามีการเผยแพร่หรือไม่ก่อนที่จะลองใช้ public class BackgroundFloobleLoader { public volatile Flooble theFlooble; public void initInBackground() { // делаем много всякого theFlooble = new Flooble(); // единственная запись в theFlooble } } public class SomeOtherClass { public void doWork() { while (true) { // чё-то там делаем... // используем theFolooble, но только если она готова if (floobleLoader.theFlooble != null) doSomething(floobleLoader.theFlooble); } } } หากการอ้างอิงถึง theFlooble ไม่เปลี่ยนแปลง โค้ดใน doWork() อาจเสี่ยงที่จะเห็น Flooble ที่สร้างขึ้นบางส่วนเมื่อพยายามอ้างอิง theFlooble ข้อกำหนดหลักสำหรับรูปแบบนี้คือ อ็อบเจ็กต์ที่เผยแพร่จะต้องปลอดภัยต่อเธรดหรือไม่สามารถเปลี่ยนรูปแบบได้อย่างมีประสิทธิผล (ไม่เปลี่ยนรูปแบบอย่างมีประสิทธิผล หมายความว่าสถานะของวัตถุจะไม่เปลี่ยนแปลงหลังจากเผยแพร่) ลิงก์ระเหยช่วยให้มั่นใจได้ว่าออบเจ็กต์จะมองเห็นได้ในรูปแบบที่เผยแพร่ แต่หากสถานะของออบเจ็กต์เปลี่ยนแปลงหลังจากการเผยแพร่ จำเป็นต้องมีการซิงโครไนซ์เพิ่มเติม
รูปแบบที่ 3: การสังเกตอย่างอิสระ
อีกตัวอย่างง่ายๆ ของการใช้สารระเหยอย่างปลอดภัยคือเมื่อมีการ "เผยแพร่" การสังเกตเป็นระยะเพื่อใช้ภายในโปรแกรม ตัวอย่างเช่นมีเซ็นเซอร์สิ่งแวดล้อมที่ตรวจจับอุณหภูมิในปัจจุบัน เธรดพื้นหลังสามารถอ่านเซ็นเซอร์นี้ได้ทุกๆ สองสามวินาที และอัปเดตตัวแปรระเหยที่มีอุณหภูมิปัจจุบัน เธรดอื่นๆ จะสามารถอ่านตัวแปรนี้ได้ โดยรู้ว่าค่าในตัวแปรนั้นอัปเดตอยู่เสมอ การใช้งานอีกประการหนึ่งสำหรับรูปแบบนี้คือการรวบรวมสถิติเกี่ยวกับโปรแกรม รายการ 4 แสดงให้เห็นว่ากลไกการรับรองความถูกต้องสามารถจดจำชื่อผู้ใช้ที่เข้าสู่ระบบครั้งล่าสุดได้อย่างไร การอ้างอิง LastUser จะถูกนำมาใช้ซ้ำเพื่อโพสต์ค่าสำหรับการใช้งานโดยส่วนที่เหลือของโปรแกรม public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; } } รูปแบบนี้จะขยายจากรูปแบบก่อนหน้า ค่าถูกเผยแพร่เพื่อใช้ที่อื่นในโปรแกรม แต่การเผยแพร่ไม่ใช่เหตุการณ์ที่เกิดขึ้นครั้งเดียว แต่เป็นชุดของเหตุการณ์อิสระ รูปแบบนี้กำหนดให้ค่าที่เผยแพร่ต้องไม่เปลี่ยนรูปอย่างมีประสิทธิผล - สถานะของค่าจะไม่เปลี่ยนแปลงหลังจากการเผยแพร่ รหัสที่ใช้ค่าจะต้องทราบว่าสามารถเปลี่ยนแปลงได้ตลอดเวลา
รูปแบบ #4: รูปแบบ “ถั่วระเหย”
รูปแบบ “volatile bean” ใช้ได้กับเฟรมเวิร์กที่ใช้ JavaBeans เป็น “glorified structs” รูปแบบ “volatile bean” ใช้ JavaBean เป็นคอนเทนเนอร์สำหรับกลุ่มของคุณสมบัติอิสระที่มี getters และ/หรือ setters เหตุผลสำหรับรูปแบบ "volatile bean" คือเฟรมเวิร์กจำนวนมากจัดเตรียมคอนเทนเนอร์สำหรับผู้ถือข้อมูลที่ไม่แน่นอน (เช่น HttpSession) แต่ออบเจ็กต์ที่วางในคอนเทนเนอร์เหล่านี้จะต้องปลอดภัยสำหรับเธรด ในรูปแบบ volatile bean องค์ประกอบข้อมูล JavaBean ทั้งหมดมีความผันผวน และ getters และ setters ควรไม่สำคัญ - ไม่ควรมีตรรกะใดๆ นอกเหนือจากการรับหรือการตั้งค่าคุณสมบัติที่เกี่ยวข้อง นอกจากนี้ สำหรับสมาชิกข้อมูลที่เป็นการอ้างอิงอ็อบเจ็กต์ อ็อบเจ็กต์ดังกล่าวจะต้องไม่เปลี่ยนรูปอย่างมีประสิทธิภาพ (การดำเนินการนี้ไม่อนุญาตให้ใช้ฟิลด์การอ้างอิงอาร์เรย์ เนื่องจากเมื่อการอ้างอิงอาร์เรย์ถูกประกาศว่าระเหย เฉพาะการอ้างอิงนั้นเท่านั้น ไม่ใช่องค์ประกอบเองที่จะมีคุณสมบัติระเหย) เช่นเดียวกับตัวแปรระเหยใดๆ ไม่สามารถมีค่าคงที่หรือข้อจำกัดที่เกี่ยวข้องกับคุณสมบัติของ JavaBeans . ตัวอย่างของ JavaBean ที่เขียนโดยใช้รูปแบบ “volatile bean” แสดงในรายการที่ 5: @ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; } }
รูปแบบความผันผวนที่ซับซ้อนมากขึ้น
รูปแบบในส่วนที่แล้วครอบคลุมกรณีทั่วไปส่วนใหญ่ที่การใช้ volatile มีความสมเหตุสมผลและชัดเจน ในส่วนนี้จะพิจารณารูปแบบที่ซับซ้อนมากขึ้นซึ่งความผันผวนสามารถให้ประโยชน์ด้านประสิทธิภาพหรือความสามารถในการขยายขนาดได้ รูปแบบความผันผวนขั้นสูงอาจมีความเปราะบางอย่างยิ่ง จำเป็นอย่างยิ่งที่สมมติฐานของคุณจะต้องได้รับการบันทึกไว้อย่างรอบคอบ และรูปแบบเหล่านี้ต้องได้รับการห่อหุ้มอย่างแน่นหนา เนื่องจากแม้แต่การเปลี่ยนแปลงที่เล็กน้อยที่สุดก็อาจทำให้โค้ดของคุณพังได้! นอกจากนี้ เนื่องจากเหตุผลหลักสำหรับกรณีการใช้งานที่มีความผันผวนที่ซับซ้อนมากขึ้นคือประสิทธิภาพ ตรวจสอบให้แน่ใจว่าคุณมีความต้องการที่ชัดเจนจริงๆ สำหรับการเพิ่มประสิทธิภาพที่ตั้งใจไว้ก่อนที่จะใช้งาน รูปแบบเหล่านี้เป็นการประนีประนอมที่เสียสละความสามารถในการอ่านหรือความสะดวกในการบำรุงรักษาเพื่อให้ได้ประสิทธิภาพที่เป็นไปได้ - หากคุณไม่ต้องการการปรับปรุงประสิทธิภาพ (หรือไม่สามารถพิสูจน์ได้ว่าคุณต้องการมันด้วยโปรแกรมการวัดที่เข้มงวด) ก็อาจเป็นข้อตกลงที่ไม่ดีเพราะว่า คุณกำลังสละสิ่งที่มีค่าและได้รับผลตอบแทนน้อยลง
รูปแบบ #5: ล็อคการอ่าน-เขียนราคาถูก
ถึงตอนนี้คุณควรตระหนักดีว่าความผันผวนนั้นอ่อนแอเกินกว่าจะใช้ตัวนับได้ เนื่องจาก ++x โดยพื้นฐานแล้วเป็นการลดการดำเนินการสามรายการ (อ่าน ต่อท้าย จัดเก็บ) หากมีสิ่งผิดปกติ คุณจะสูญเสียค่าที่อัปเดตหากหลายเธรดพยายามเพิ่มตัวนับที่ระเหยได้ในเวลาเดียวกัน อย่างไรก็ตาม หากมีการอ่านมากกว่าการเปลี่ยนแปลงอย่างมีนัยสำคัญ คุณสามารถรวมการล็อกภายในและตัวแปรที่ระเหยได้เพื่อลดค่าใช้จ่ายของเส้นทางโค้ดโดยรวม รายการ 6 แสดงตัวนับเธรดที่ปลอดภัยซึ่งใช้การซิงโครไนซ์เพื่อให้แน่ใจว่าการดำเนินการที่เพิ่มขึ้นเป็นแบบอะตอมมิก และใช้ความผันผวนเพื่อให้แน่ใจว่าผลลัพธ์ปัจจุบันจะมองเห็นได้ หากการอัพเดตไม่บ่อยนัก วิธีการนี้สามารถปรับปรุงประสิทธิภาพได้ เนื่องจากค่าใช้จ่ายในการอ่านถูกจำกัดไว้ที่การอ่านแบบผันผวน ซึ่งโดยทั่วไปจะมีราคาถูกกว่าการได้มาซึ่งการล็อคที่ไม่ขัดแย้งกัน @ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; } } เหตุผลที่วิธีนี้เรียกว่า "ล็อคการอ่าน-เขียนราคาถูก" เป็นเพราะคุณใช้กลไกการกำหนดเวลาที่แตกต่างกันสำหรับการอ่านและเขียน เนื่องจากการดำเนินการเขียนในกรณีนี้ละเมิดเงื่อนไขแรกของการใช้ volatile คุณจึงไม่สามารถใช้ volatile เพื่อใช้งานตัวนับได้อย่างปลอดภัย - คุณต้องใช้การล็อค อย่างไรก็ตาม คุณสามารถใช้การระเหยเพื่อทำให้ค่าปัจจุบันมองเห็นได้เมื่ออ่าน ดังนั้นคุณจึงใช้การล็อกสำหรับการดำเนินการแก้ไขทั้งหมด และใช้การระเหยสำหรับการดำเนินการอ่านอย่างเดียว หากการล็อคอนุญาตให้เข้าถึงค่าได้ครั้งละหนึ่งเธรดเท่านั้น การอ่านแบบระเหยจะอนุญาตมากกว่าหนึ่งรายการ ดังนั้นเมื่อคุณใช้การระเหยเพื่อป้องกันการอ่าน คุณจะได้รับการแลกเปลี่ยนในระดับที่สูงกว่าการใช้การล็อคกับโค้ดทั้งหมด: และ อ่าน และบันทึก อย่างไรก็ตาม โปรดระวังความเปราะบางของรูปแบบนี้: ด้วยกลไกการซิงโครไนซ์ที่แข่งขันกันสองกลไก มันอาจจะซับซ้อนมากหากคุณใช้มากกว่าการใช้งานขั้นพื้นฐานที่สุดของรูปแบบนี้
สรุป
ตัวแปรระเหยเป็นรูปแบบการซิงโครไนซ์ที่เรียบง่ายกว่าแต่อ่อนกว่าการล็อค ซึ่งในบางกรณีให้ประสิทธิภาพหรือความสามารถในการขยายได้ดีกว่าการล็อคจากภายใน หากคุณตรงตามเงื่อนไขสำหรับการใช้สารระเหยอย่างปลอดภัย - ตัวแปรนั้นเป็นอิสระอย่างแท้จริงจากทั้งตัวแปรอื่น ๆ และค่าก่อนหน้าของมันเอง - บางครั้งคุณสามารถทำให้โค้ดง่ายขึ้นโดยการแทนที่การซิงโครไนซ์ด้วยสารระเหย อย่างไรก็ตาม รหัสที่ใช้ความผันผวนมักจะเปราะบางกว่ารหัสที่ใช้การล็อค รูปแบบที่แนะนำในที่นี้ครอบคลุมกรณีที่พบบ่อยที่สุดซึ่งความผันผวนเป็นทางเลือกที่สมเหตุสมผลในการซิงโครไนซ์ โดยการปฏิบัติตามรูปแบบเหล่านี้ - และดูแลไม่ให้เกินขีดจำกัดของตัวเอง - คุณสามารถใช้ volatile ได้อย่างปลอดภัยในกรณีที่รูปแบบเหล่านี้ให้ผลประโยชน์
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION