คำหลักที่ระเหยง่ายมีประโยชน์สำหรับอะไร


672

ที่ทำงานวันนี้ฉันเจอvolatileคำค้นหาในชวา ไม่คุ้นเคยกับมันฉันพบคำอธิบายนี้:

ทฤษฎีและการปฏิบัติของ Java: การจัดการความผันผวน

ระบุรายละเอียดที่บทความนั้นอธิบายคำหลักที่เป็นปัญหาคุณเคยใช้หรือเคยเห็นกรณีที่คุณสามารถใช้คำหลักนี้ในลักษณะที่ถูกต้องหรือไม่

คำตอบ:


742

volatileมีความหมายสำหรับการมองเห็นหน่วยความจำ โดยทั่วไปแล้วค่าของvolatileฟิลด์จะปรากฏให้ผู้อ่านทุกคนเห็น (โดยเฉพาะเธรดอื่น ๆ ) หลังจากการดำเนินการเขียนเสร็จสมบูรณ์ หากไม่มีvolatileผู้อ่านจะเห็นค่าที่ไม่อัปเดต

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

หนังสือ " Java Concurrency ในการปฏิบัติ " volatileซึ่งผมขอแนะนำให้คำอธิบายที่ดีของ หนังสือเล่มนี้เขียนโดยบุคคลเดียวกันกับผู้เขียนบทความ IBM ที่อ้างอิงในคำถาม (อันที่จริงเขาอ้างอิงหนังสือของเขาที่ด้านล่างของบทความนั้น) การใช้งานของฉันvolatileคือสิ่งที่บทความของเขาเรียกว่า "รูปแบบสถานะ 1 สถานะ"

หากคุณต้องการที่จะเรียนรู้เพิ่มเติมเกี่ยวกับวิธีvolatileการทำงานภายใต้ประทุนอ่านบนรุ่นหน่วยความจำ Java หากคุณต้องการก้าวข้ามระดับนั้นลองอ่านหนังสือสถาปัตยกรรมคอมพิวเตอร์ที่ดีเช่นHennessy & Pattersonและอ่านเกี่ยวกับการเชื่อมโยงกันของแคชและความสอดคล้องของแคช


118
คำตอบนี้ถูกต้อง แต่ไม่สมบูรณ์ มันละเว้นคุณสมบัติที่สำคัญของvolatileที่มาพร้อมกับ Java Memory Model ใหม่ที่กำหนดใน JSR 133: เมื่อเธรดอ่านvolatileตัวแปรมันจะเห็นเฉพาะค่าที่เขียนไปล่าสุดโดยเธรดอื่น แต่ยังเขียนอื่น ๆ ไปยังตัวแปรอื่นที่ สามารถมองเห็นได้ในเธรดอื่นในขณะที่volatileเขียน ดูคำตอบนี้และข้อมูลอ้างอิงนี้
Adam Zalcman

46
สำหรับผู้เริ่มต้นฉันขอให้คุณสาธิตด้วยรหัส (โปรด?)
Hungry Blue Dev

6
บทความที่เชื่อมโยงในคำถามมีตัวอย่างรหัส
Greg Mattes

ฉันคิดว่าลิงก์ 'Hennessy & Patterson' เสียหาย และลิงก์ไปยัง 'โมเดลหน่วยความจำ Java' จะนำไปสู่ข้อกำหนดภาษา Java ของ Oracle 'บทที่ 17 เธรดและล็อก'
กริช

2
@fefrei:“ ทันที” เป็นคำศัพท์ แน่นอนว่าไม่สามารถรับประกันได้ว่าจะไม่มีการกำหนดเวลาดำเนินการหรืออัลกอริธึมการกำหนดตารางเวลาสำหรับเธรด วิธีเดียวที่โปรแกรมจะค้นหาว่าการอ่านแบบระเหยนั้นเกิดขึ้นภายหลังจากการเขียนแบบระเหยโดยเฉพาะหรือไม่คือการตรวจสอบว่าค่าที่เห็นเป็นค่าที่เขียนไว้หรือไม่
Holger

177

“ …ตัวดัดแปลงระเหยรับประกันได้ว่าเธรดใด ๆ ที่อ่านฟิลด์จะเห็นค่าที่เขียนล่าสุด” - Josh Bloch

ถ้าคุณคิดจะใช้volatileให้อ่านแพ็คเกจjava.util.concurrentที่เกี่ยวข้องกับพฤติกรรมของอะตอม

โพสต์ Wikipedia บนรูปแบบซิงเกิลแสดงให้เห็นถึงความผันผวนในการใช้


18
ทำไมถึงมีทั้งคำหลักvolatileและsynchronized?
ptkato

5
บทความ Wikipedia ในรูปแบบซิงเกิลมีการเปลี่ยนแปลงมากมายตั้งแต่นั้นเป็นต้นมาและไม่มีฟีเจอร์ที่กล่าวถึงvolatileตัวอย่างอีกต่อไป มันสามารถพบได้ในรุ่นที่เก็บไว้
bskp

1
@ptkato คำหลักทั้งสองนั้นมีจุดประสงค์ที่แตกต่างกันอย่างสิ้นเชิงดังนั้นคำถามจึงไม่สมเหตุสมผลนักเมื่อเปรียบเทียบกัน มันเหมือนกับการพูดว่า "ทำไมจึงมีทั้งคำหลักvoidและpublicคำหลัก"
DavidS

134

จุดสำคัญเกี่ยวกับvolatile:

  1. การประสานข้อมูลในชวาเป็นไปได้โดยใช้คำหลัก Java synchronizedและvolatileและล็อค
  2. ใน Java เราไม่สามารถมีsynchronizedตัวแปรได้ การใช้synchronizedคำหลักที่มีตัวแปรนั้นผิดกฎหมายและจะทำให้เกิดข้อผิดพลาดในการรวบรวม แทนที่จะใช้synchronizedตัวแปรใน Java คุณสามารถใช้volatileตัวแปรjava ซึ่งจะสั่งให้เธรด JVM อ่านค่าของvolatileตัวแปรจากหน่วยความจำหลักและไม่แคชในเครื่อง
  3. หากตัวแปรไม่ถูกแชร์ระหว่างหลายเธรดไม่จำเป็นต้องใช้volatileคำหลัก

แหล่ง

ตัวอย่างการใช้งานของvolatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

เรากำลังสร้างอินสแตนซ์อย่างเกียจคร้านในเวลาที่คำขอแรกมาถึง

หากเราไม่สร้าง_instanceตัวแปรvolatileดังนั้นเธรดที่สร้างอินสแตนซ์ของSingletonจะไม่สามารถสื่อสารกับเธรดอื่นได้ ดังนั้นหากเธรด A กำลังสร้างอินสแตนซ์ของ Singleton และหลังจากการสร้าง CPU จะเกิดความเสียหาย ฯลฯ เธรดอื่น ๆ ทั้งหมดจะไม่สามารถมองเห็นค่า_instanceที่ไม่เป็นโมฆะและพวกเขาเชื่อว่ามันยังคงถูกกำหนดเป็นโมฆะ

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

สรุป : volatileคำสำคัญยังใช้เพื่อสื่อสารเนื้อหาของหน่วยความจำระหว่างเธรด

ตัวอย่างการใช้งานที่ไม่มีความผันผวน:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

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

ป้อนคำอธิบายรูปภาพที่นี่

volatileการใช้งานใน Java :

โดยทั่วไปตัวทำซ้ำที่ล้มเหลวอย่างรวดเร็วจะดำเนินการโดยใช้ตัวvolatileนับบนวัตถุรายการ

  • เมื่อรายการถูกปรับปรุงตัวนับจะเพิ่มขึ้น
  • เมื่อIteratorสร้างขึ้นค่าปัจจุบันของตัวนับจะถูกฝังในIteratorวัตถุ
  • เมื่อIteratorทำการดำเนินการเมธอดจะเปรียบเทียบค่าตัวนับทั้งสองและส่ง a ConcurrentModificationExceptionหากมีความแตกต่าง

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


2
"ตัววนซ้ำที่ล้มเหลวเร็วถูกนำมาใช้โดยใช้ตัวนับระเหย" - ไม่มีอีกต่อไปราคาแพงเกินไป: bugs.java.com/bugdatabase/view_bug.do?bug_id=6625725
Vsevolod Golovanov

การตรวจสอบ _instance ปลอดภัยซ้ำซ้อนหรือไม่ ฉันคิดว่าพวกเขาไม่ปลอดภัยแม้จะมีความผันผวน
Dexters

"ซึ่งจะสั่งให้เธรด JVM อ่านค่าของตัวแปรระเหยจากหน่วยความจำหลักและห้ามแคชในเครื่อง" จุดดี
Humoyun Ahmad

เพื่อความปลอดภัยด้ายสามารถไปด้วยprivate static final Singleton _instance;เช่นกัน
Chris311

53

volatile มีประโยชน์มากในการหยุดเธรด

ไม่ใช่ว่าคุณควรจะเขียนเธรดของคุณเอง Java 1.6 มีเธรดพูลจำนวนมาก แต่ถ้าคุณแน่ใจว่าคุณต้องการเธรดคุณจะต้องรู้วิธีหยุดมัน

รูปแบบที่ฉันใช้สำหรับเธรดคือ:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

ในส่วนโค้ดข้างต้นด้ายอ่านในขณะที่วงจะแตกต่างจากคนที่โทรclose close()เธรดที่รันลูปอาจไม่เคยเห็นการเปลี่ยนแปลงใด ๆ

สังเกตว่าไม่จำเป็นต้องทำการซิงโครไนซ์


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

27
@ โจริคุณต้องการความผันผวนเนื่องจากการอ่านเธรดในขณะที่ลูปแตกต่างจากการโทรที่ปิด () เธรดที่รันลูปอาจไม่เคยเห็นการเปลี่ยนแปลงใด ๆ
Pyrolistical

คุณจะบอกว่ามีข้อได้เปรียบระหว่างการหยุดเธรดเช่นนั้นหรือใช้วิธีการ Thread # interrupt () และ Thread # isInterrupted ()?
Ricardo Belchior

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

2
@aroth: ด้วย JVM วันนี้คุณสามารถสังเกตได้ว่าในทางปฏิบัติแม้จะมีตัวอย่างที่ง่ายที่สุด แต่คุณไม่สามารถสร้างพฤติกรรมนี้ได้อย่างน่าเชื่อถือ ด้วยแอพพลิเคชั่นที่ซับซ้อนมากขึ้นบางครั้งคุณมีการกระทำอื่น ๆ ด้วยการรับประกันการมองเห็นหน่วยความจำภายในรหัสของคุณซึ่งทำให้มันเกิดขึ้นกับการทำงานซึ่งเป็นอันตรายโดยเฉพาะอย่างยิ่งเพราะคุณไม่รู้ว่าทำไมมันทำงานอย่างไร แอปพลิเคชัน ...
Holger

31

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


27

ตัวแปรที่ประกาศพร้อมกับvolatileคำหลักมีคุณสมบัติหลักสองประการที่ทำให้เป็นพิเศษ

  1. ถ้าเรามีตัวแปรที่ระเหยได้มันจะไม่สามารถแคชลงในหน่วยความจำแคชของไมโครคอมพิวเตอร์ได้ การเข้าถึงเกิดขึ้นเสมอจากหน่วยความจำหลัก

  2. หากมีการดำเนินการเขียนไปในตัวแปรระเหยและทันใดนั้นการดำเนินการอ่านมีการร้องขอจะรับประกันว่าการดำเนินการเขียนจะแล้วเสร็จก่อนที่จะมีการดำเนินการอ่าน

สองคุณสมบัติข้างต้นอนุมานได้ว่า

  • กระทู้ทั้งหมดที่อ่านตัวแปรระเหยจะอ่านค่าล่าสุดอย่างแน่นอน เพราะไม่มีค่าแคชสามารถก่อให้เกิดมลพิษ และการร้องขอการอ่านจะได้รับหลังจากเสร็จสิ้นการดำเนินการเขียนปัจจุบันเท่านั้น

และในทางกลับกัน

  • หากเราตรวจสอบเพิ่มเติม# 2ที่ฉันได้กล่าวถึงต่อไปเราจะเห็นว่าvolatileคำหลักเป็นวิธีที่เหมาะในการรักษาตัวแปรที่ใช้ร่วมกันซึ่งมีจำนวนเธรดเครื่องอ่าน 'n' และมีเพียงหนึ่งเธรดนักเขียนเพื่อเข้าถึง เมื่อเราเพิ่มvolatileคำหลักเสร็จแล้วก็จะทำ ไม่มีค่าใช้จ่ายอื่นใดเกี่ยวกับความปลอดภัยของด้าย

Conversly,

เราไม่สามารถทำให้การใช้volatileคำหลัก แต่เพียงผู้เดียวเพื่อตอบสนองความตัวแปรที่ใช้ร่วมกันซึ่งมีหัวข้อนักเขียนมากกว่าหนึ่งเข้าถึง


3
สิ่งนี้อธิบายความแตกต่างระหว่างการระเหยและการซิงโครไนซ์
ajay

13

ไม่มีใครพูดถึงการรักษาการอ่านและการเขียนสำหรับตัวแปรชนิดยาวและคู่ การอ่านและเขียนเป็นการดำเนินการแบบปรมาณูสำหรับตัวแปรอ้างอิงและสำหรับตัวแปรดั้งเดิมส่วนใหญ่ยกเว้นสำหรับชนิดตัวแปรแบบยาวและแบบคู่ซึ่งต้องใช้คำสำคัญแบบระเหยเพื่อการดำเนินการแบบอะตอมมิก @link


เพื่อให้ชัดเจนยิ่งขึ้นไม่มีความจำเป็นที่จะต้องตั้งค่าบูลีนที่ระเหยได้เนื่องจากการอ่านและเขียนของบูลีนเป็นแบบอะตอมอยู่แล้ว
Kai Wang

2
@KaiWang คุณไม่จำเป็นต้องใช้ความผันผวนกับบูลีนเพื่อวัตถุประสงค์ของอะตอมมิก แต่คุณอาจมีเหตุผลในการมองเห็นอย่างแน่นอน นั่นคือสิ่งที่คุณหมายถึงจะพูด?
SusanW

12

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


10

ในความคิดของฉันมีสองสถานการณ์ที่สำคัญอื่น ๆ นอกเหนือจากการหยุดเธรดซึ่งใช้คำหลักระเหยง่ายคือ:

  1. กลไกการล็อคตรวจสอบอีกครั้ง มักใช้ในรูปแบบการออกแบบซิงเกิล ในนี้วัตถุเดี่ยวจะต้องมีการประกาศระเหย
  2. ปลอม wakeups บางครั้งเธรดอาจปลุกจากการรอสายแม้ว่าจะไม่มีการเรียกการแจ้งเตือน พฤติกรรมนี้เรียกว่าการปลุกปลอม สิ่งนี้สามารถโต้กลับได้โดยใช้ตัวแปรเงื่อนไข (แฟล็กบูลีน) ใส่การเรียก wait () ในขณะที่วนซ้ำตราบใดที่มีค่าสถานะเป็นจริง ดังนั้นหากเธรดตื่นขึ้นจากการรอสายเนื่องจากสาเหตุอื่นที่ไม่ใช่ Notify / NotifyAll ก็จะพบการตั้งค่าสถานะยังคงเป็นจริงและดังนั้นจึงเรียกรออีกครั้ง ก่อนการแจ้งเตือนการโทรให้ตั้งค่าสถานะนี้เป็นจริง ในกรณีนี้ธงบูลีนถูกประกาศเป็นสารระเหย

ส่วนที่ # 2 ดูเหมือนสับสนมากมันเป็นการแจ้งเตือนที่หายไปการปลุกซ้ำ ๆ และปัญหาการมองเห็นของหน่วยความจำ นอกจากนี้หากการใช้งานธงทั้งหมดมีการซิงโครไนซ์ความผันผวนจะซ้ำซ้อน ฉันคิดว่าฉันได้รับคะแนนของคุณ แต่การปลุกเสแสร้งไม่ใช่คำที่ถูกต้อง กรุณาชี้แจง
Nathan Hughes

5

คุณจะต้องใช้คำหลัก 'ระเหย' หรือ 'ทำข้อมูลให้ตรงกัน' และเครื่องมือและเทคนิคการควบคุมภาวะพร้อมกันอื่น ๆ ที่คุณมีเมื่อคุณต้องการพัฒนาแอพพลิเคชั่นแบบมัลติเธรด ตัวอย่างของแอปพลิเคชันดังกล่าวคือแอพเดสก์ท็อป

หากคุณกำลังพัฒนาแอปพลิเคชันที่จะปรับใช้กับแอปพลิเคชันเซิร์ฟเวอร์ (Tomcat, JBoss AS, Glassfish และอื่น ๆ ) คุณไม่จำเป็นต้องจัดการกับการควบคุมภาวะพร้อมกันตามที่ได้รับการจัดการโดยแอปพลิเคชันเซิร์ฟเวอร์ ในความเป็นจริงถ้าฉันจำได้อย่างถูกต้องมาตรฐาน Java EE ห้ามควบคุมการทำงานพร้อมกันใน servlets และ EJBs เพราะมันเป็นส่วนหนึ่งของ 'โครงสร้างพื้นฐาน' เลเยอร์ที่คุณควรจะเป็นอิสระจากการจัดการมัน คุณสามารถควบคุมการทำงานพร้อมกันในแอพดังกล่าวเฉพาะในกรณีที่คุณใช้วัตถุเดี่ยว แม้ว่าคุณจะถักส่วนประกอบของคุณโดยใช้ frameworkd เช่น Spring

ดังนั้นในกรณีส่วนใหญ่ของการพัฒนา Java ที่แอปพลิเคชันเป็นเว็บแอปพลิเคชันและใช้ IoC framework เช่น Spring หรือ EJB คุณไม่จำเป็นต้องใช้ 'volatile'


5

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

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

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

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

4

ใช่ฉันใช้มันค่อนข้างมาก - มันมีประโยชน์มากสำหรับโค้ดแบบมัลติเธรด บทความที่คุณชี้ว่าเป็นบทความที่ดี แม้ว่าจะมีสองสิ่งสำคัญที่ต้องคำนึงถึง:

  1. คุณควรใช้สารระเหยถ้าคุณเข้าใจอย่างสมบูรณ์ว่ามันทำอะไรและแตกต่างกันอย่างไรในการซิงโครไนซ์ ในหลาย ๆ สถานการณ์ความผันผวนจะปรากฏขึ้นบนพื้นผิวเพื่อเป็นทางเลือกที่ง่ายกว่าในการซิงโครไนซ์เมื่อบ่อยครั้งที่ความเข้าใจที่ดีขึ้นเกี่ยวกับการระเหยจะทำให้ชัดเจนว่าการซิงโครไนซ์เป็นทางเลือกเดียวเท่านั้น
  2. สารระเหยไม่ได้ทำงานใน JVM รุ่นเก่าจำนวนมากถึงแม้ว่าการซิงโครไนซ์จะทำ ฉันจำได้ว่าเห็นเอกสารที่อ้างอิงระดับต่าง ๆ ของการสนับสนุนใน JVM ที่แตกต่างกัน แต่น่าเสียดายที่ตอนนี้ฉันหามันไม่เจอ ดูอย่างแน่นอนหากคุณใช้ Java pre 1.5 หรือหากคุณไม่ได้ควบคุม JVM ที่โปรแกรมของคุณจะทำงาน

4

ทุกเธรดที่เข้าถึงเขตข้อมูลระเหยจะอ่านค่าปัจจุบันก่อนดำเนินการต่อแทนการใช้แคช (อาจมีค่า)

ตัวแปรสมาชิกเท่านั้นที่สามารถเปลี่ยนแปลงได้หรือชั่วคราว


3

ใช่แน่นอน (และไม่ใช่เฉพาะใน Java แต่ยังอยู่ใน C #) มีบางครั้งที่คุณจำเป็นต้องได้รับหรือตั้งค่าที่รับประกันว่าเป็นการปฏิบัติการแบบปรมาณูบนแพลตฟอร์มที่คุณได้รับตัวอย่างเช่น int หรือบูลีน แต่ไม่ต้องการ ด้านบนของเธรดล็อก คำสำคัญระเหยช่วยให้คุณมั่นใจได้ว่าเมื่อคุณอ่านค่าที่คุณได้รับมูลค่าปัจจุบันและไม่ใช่ค่าแคชที่เพิ่งล้าสมัยโดยการเขียนในหัวข้ออื่น


3

มีการใช้คำหลักระเหยง่ายสองแบบ

  1. ป้องกัน JVM จากการอ่านค่าจาก register (ถือว่าเป็นแคช) และบังคับให้ค่าอ่านจากหน่วยความจำ
  2. ลดความเสี่ยงของข้อผิดพลาดที่ไม่สอดคล้องของหน่วยความจำ

ป้องกัน JVM จากการอ่านค่าในรีจิสเตอร์และบังคับให้ค่าอ่านจากหน่วยความจำ

ธงยุ่งถูกนำมาใช้เพื่อป้องกันไม่ให้ด้ายจากการศึกษาในขณะที่อุปกรณ์ไม่ว่างและธงที่ไม่ได้ป้องกันโดยล็อค:

while (busy) {
    /* do something else */
}

เธรดการทดสอบจะดำเนินการต่อเมื่อเธรดอื่นปิดสถานะไม่ว่าง :

busy = 0;

อย่างไรก็ตามเนื่องจากการเข้าถึงไม่ว่างในเธรดการทดสอบบ่อยครั้ง JVM อาจปรับการทดสอบให้เหมาะสมโดยการกำหนดค่าของการยุ่งในการลงทะเบียนจากนั้นทดสอบเนื้อหาของการลงทะเบียนโดยไม่ต้องอ่านค่าของการยุ่งในหน่วยความจำก่อนการทดสอบทุกครั้ง เธรดการทดสอบจะไม่เห็นการเปลี่ยนแปลงที่ยุ่งและเธรดอื่นจะเปลี่ยนค่าของการยุ่งในหน่วยความจำเท่านั้นทำให้เกิดการหยุดชะงัก การประกาศสถานะไม่ว่างเนื่องจากความผันผวนจะบังคับให้อ่านค่าก่อนการทดสอบแต่ละครั้ง

ลดความเสี่ยงของข้อผิดพลาดความสอดคล้องของหน่วยความจำ

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

เทคนิคการอ่านการเขียนโดยไม่มีข้อผิดพลาดความสอดคล้องของหน่วยความจำเรียกว่าการกระทำของอะตอมการกระทำของอะตอม

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

ด้านล่างนี้คือการกระทำที่คุณสามารถระบุได้ว่าเป็นอะตอมมิก:

  • การอ่านและเขียนเป็นอะตอมมิกสำหรับตัวแปรอ้างอิงและสำหรับตัวแปรดั้งเดิมส่วนใหญ่ (ทุกประเภทยกเว้นแบบยาวและแบบคู่)
  • การอ่านและการเขียนเป็นแบบอะตอมมิกสำหรับตัวแปรทั้งหมดที่ประกาศความผันผวน (รวมถึงตัวแปรแบบยาวและแบบคู่)

ไชโย!


3

volatileสำหรับโปรแกรมเมอร์ว่าค่านั้นจะเป็นปัจจุบันเสมอ ปัญหาคือค่าที่สามารถบันทึกในหน่วยความจำฮาร์ดแวร์ชนิดต่าง ๆ ตัวอย่างเช่นสามารถลงทะเบียน CPU, แคช CPU, RAM ... СPUลงทะเบียนและแคช CPU เป็นของ CPU และไม่สามารถแบ่งปันข้อมูลที่แตกต่างจาก RAM ที่อยู่ในการช่วยเหลือในการใช้พลังงานหลายเธรด

ป้อนคำอธิบายรูปภาพที่นี่

volatilekeyword ระบุว่าตัวแปรจะถูกอ่านและเขียนจาก / ถึงหน่วยความจำ RAM โดยตรงโดยตรงมันมีร่องรอยการคำนวณบางอย่าง

Java 5ขยายvolatileโดยการสนับสนุนhappens-before[เกี่ยวกับ]

การเขียนไปยังเขตข้อมูลระเหยเกิดขึ้นก่อนที่จะอ่านทุกครั้งของสนามที่ตามมา

volatileคำหลักที่ไม่ได้รักษาrace conditionสถานการณ์เมื่อหลายหัวข้อสามารถเขียนค่าบางอย่างไปพร้อม ๆ กัน คำตอบคือsynchronizedคำสำคัญ[เกี่ยวกับ]

ดังนั้นความปลอดภัยก็ต่อเมื่อหนึ่งเธรดที่เขียนและอื่น ๆ เพียงแค่อ่านvolatileค่า

ระเหยกับซิงโครไนซ์


2

สารระเหยทำตาม

1> อ่านและเขียนตัวแปรที่ผันแปรได้โดยเธรดที่แตกต่างกันจะมาจากหน่วยความจำเสมอไม่ใช่จากแคชของเธรดหรือการลงทะเบียน cpu ดังนั้นแต่ละเธรดจะเกี่ยวข้องกับค่าล่าสุดเสมอ 2> เมื่อ 2 เธรดที่แตกต่างกันทำงานกับอินสแตนซ์เดียวกันหรือตัวแปรคงที่ในฮีปหนึ่งอาจเห็นการกระทำของผู้อื่นตามลำดับ ดูบล็อกของเจเรมีแมนสันเกี่ยวกับเรื่องนี้ แต่สารระเหยช่วยได้ที่นี่

การติดตามโค้ดที่รันเต็มที่แสดงให้เห็นว่าจำนวนเธรดสามารถดำเนินการตามลำดับที่กำหนดไว้ล่วงหน้าและพิมพ์ผลลัพธ์ได้อย่างไรโดยไม่ต้องใช้คีย์เวิร์ดที่ซิงโครไนซ์

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

เพื่อให้บรรลุถึงสิ่งนี้เราอาจใช้รหัสที่ทำงานเต็มต่อไปนี้

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

ลิงค์ GitHub ต่อไปนี้มี readme ซึ่งให้คำอธิบายที่เหมาะสม https://github.com/sankar4git/volatile_thread_ordering


1

จากหน้าเอกสารของ oracle ความต้องการตัวแปรระเหยเกิดขึ้นเพื่อแก้ไขปัญหาความสอดคล้องของหน่วยความจำ:

การใช้ตัวแปรระเหยช่วยลดความเสี่ยงของข้อผิดพลาดความสอดคล้องของหน่วยความจำเพราะการเขียนใด ๆ ไปยังตัวแปรระเหยสร้างความสัมพันธ์ที่เกิดขึ้นก่อนที่จะมีการอ่านตัวแปรเดียวกันที่ตามมา

ซึ่งหมายความว่าการเปลี่ยนแปลงvolatileตัวแปรสามารถมองเห็นกระทู้อื่น ๆ ได้เสมอ นอกจากนี้ยังหมายความว่าเมื่อเธรดอ่านตัวแปรระเหยมันไม่เพียง แต่เห็นการเปลี่ยนแปลงล่าสุดไปยังตัวแปรvolatileแต่ยังมีผลข้างเคียงของโค้ดที่นำไปสู่การเปลี่ยนแปลง

ตามที่อธิบายในPeter Parkerคำตอบหากไม่มีvolatileตัวแก้ไขสแต็กของแต่ละเธรดอาจมีสำเนาของตัวแปรของตัวเอง โดยทำให้ตัวแปรเป็นvolatileปัญหาความสอดคล้องของหน่วยความจำได้รับการแก้ไข

ดูที่หน้าการสอนของjenkovเพื่อความเข้าใจที่ดีขึ้น

ดูคำถาม SE ที่เกี่ยวข้องเพื่อดูรายละเอียดเพิ่มเติมเกี่ยวกับการระเหย & การใช้เคสเพื่อการระเหย:

ความแตกต่างระหว่างสารระเหยและซิงโครไนซ์ใน Java

กรณีใช้งานจริงหนึ่งกรณี:

java.text.SimpleDateFormat("HH-mm-ss")คุณมีกระทู้จำนวนมากซึ่งต้องพิมพ์เวลาปัจจุบันในรูปแบบเฉพาะตัวอย่างเช่น: Yon สามารถมีคลาสได้หนึ่งคลาสซึ่งแปลงเวลาปัจจุบันเป็นSimpleDateFormatและอัปเดตตัวแปรสำหรับทุก ๆ หนึ่งวินาที หัวข้ออื่น ๆ ทั้งหมดสามารถใช้ตัวแปรระเหยนี้เพื่อพิมพ์เวลาปัจจุบันในล็อกไฟล์


1

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


-1

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

ถ้าการเปลี่ยนแปลงเกิดขึ้น 1 เธรดและสิ่งอื่น ๆ เพียงแค่อ่านค่านี้ความผันผวนจะเหมาะสม


-1

โดยทั่วไปแล้วตัวแปรระเหยจะใช้สำหรับการอัปเดตทันที (ล้างข้อมูล) ในบรรทัดแคชที่ใช้ร่วมกันหลักเมื่อมีการอัปเดตเพื่อให้การเปลี่ยนแปลงสะท้อนไปที่เธรดผู้ทำงานทั้งหมดทันที


-2

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

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

เมื่อvolatileไม่ได้ใช้งาน:คุณจะไม่เห็นข้อความ ' หยุดอยู่ที่: xxx ' แม้หลังจาก ' หยุดทำงาน: xxx ' และโปรแกรมยังคงทำงานต่อไป

Stopping on: 1895303906650500

เมื่อvolatileใช้แล้ว:คุณจะเห็นข้อความ ' หยุดอยู่: xxx ' ทันที

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

การสาธิต: https://repl.it/repls/SilverAgonizingObjectcode


เพื่อ downvoter: สนใจที่จะอธิบายว่าทำไม downvote หากนี่ไม่เป็นความจริงอย่างน้อยฉันก็จะได้เรียนรู้สิ่งที่ผิด ฉันได้เพิ่มความคิดเห็นแบบเดียวกันนี้สองครั้ง แต่ไม่ทราบว่าใครกำลังลบอีกครั้งและอีกครั้ง
manikanta

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