ที่ทำงานวันนี้ฉันเจอvolatile
คำค้นหาในชวา ไม่คุ้นเคยกับมันฉันพบคำอธิบายนี้:
ระบุรายละเอียดที่บทความนั้นอธิบายคำหลักที่เป็นปัญหาคุณเคยใช้หรือเคยเห็นกรณีที่คุณสามารถใช้คำหลักนี้ในลักษณะที่ถูกต้องหรือไม่
ที่ทำงานวันนี้ฉันเจอvolatile
คำค้นหาในชวา ไม่คุ้นเคยกับมันฉันพบคำอธิบายนี้:
ระบุรายละเอียดที่บทความนั้นอธิบายคำหลักที่เป็นปัญหาคุณเคยใช้หรือเคยเห็นกรณีที่คุณสามารถใช้คำหลักนี้ในลักษณะที่ถูกต้องหรือไม่
คำตอบ:
volatile
มีความหมายสำหรับการมองเห็นหน่วยความจำ โดยทั่วไปแล้วค่าของvolatile
ฟิลด์จะปรากฏให้ผู้อ่านทุกคนเห็น (โดยเฉพาะเธรดอื่น ๆ ) หลังจากการดำเนินการเขียนเสร็จสมบูรณ์ หากไม่มีvolatile
ผู้อ่านจะเห็นค่าที่ไม่อัปเดต
เพื่อตอบคำถามของคุณ: ใช่ฉันใช้volatile
ตัวแปรเพื่อควบคุมว่าบางรหัสยังคงวนซ้ำ ห่วงทดสอบค่าและยังคงถ้ามันเป็นvolatile
true
สามารถตั้งเงื่อนไขเป็นfalse
โดยเรียกวิธี "หยุด" ลูปจะเห็นfalse
และสิ้นสุดลงเมื่อทำการทดสอบค่าหลังจากวิธีการหยุดการดำเนินการเสร็จสมบูรณ์
หนังสือ " Java Concurrency ในการปฏิบัติ " volatile
ซึ่งผมขอแนะนำให้คำอธิบายที่ดีของ หนังสือเล่มนี้เขียนโดยบุคคลเดียวกันกับผู้เขียนบทความ IBM ที่อ้างอิงในคำถาม (อันที่จริงเขาอ้างอิงหนังสือของเขาที่ด้านล่างของบทความนั้น) การใช้งานของฉันvolatile
คือสิ่งที่บทความของเขาเรียกว่า "รูปแบบสถานะ 1 สถานะ"
หากคุณต้องการที่จะเรียนรู้เพิ่มเติมเกี่ยวกับวิธีvolatile
การทำงานภายใต้ประทุนอ่านบนรุ่นหน่วยความจำ Java หากคุณต้องการก้าวข้ามระดับนั้นลองอ่านหนังสือสถาปัตยกรรมคอมพิวเตอร์ที่ดีเช่นHennessy & Pattersonและอ่านเกี่ยวกับการเชื่อมโยงกันของแคชและความสอดคล้องของแคช
“ …ตัวดัดแปลงระเหยรับประกันได้ว่าเธรดใด ๆ ที่อ่านฟิลด์จะเห็นค่าที่เขียนล่าสุด” - Josh Bloch
ถ้าคุณคิดจะใช้volatile
ให้อ่านแพ็คเกจjava.util.concurrent
ที่เกี่ยวข้องกับพฤติกรรมของอะตอม
โพสต์ Wikipedia บนรูปแบบซิงเกิลแสดงให้เห็นถึงความผันผวนในการใช้
volatile
และsynchronized
?
volatile
ตัวอย่างอีกต่อไป มันสามารถพบได้ในรุ่นที่เก็บไว้
void
และpublic
คำหลัก"
จุดสำคัญเกี่ยวกับvolatile
:
synchronized
และvolatile
และล็อคsynchronized
ตัวแปรได้ การใช้synchronized
คำหลักที่มีตัวแปรนั้นผิดกฎหมายและจะทำให้เกิดข้อผิดพลาดในการรวบรวม แทนที่จะใช้synchronized
ตัวแปรใน Java คุณสามารถใช้volatile
ตัวแปรjava ซึ่งจะสั่งให้เธรด JVM อ่านค่าของvolatile
ตัวแปรจากหน่วยความจำหลักและไม่แคชในเครื่อง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
หากมีความแตกต่างการใช้งานตัวทำซ้ำที่ไม่ปลอดภัยมักจะมีน้ำหนักเบา พวกเขามักจะพึ่งพาคุณสมบัติของโครงสร้างข้อมูลการใช้งานรายการเฉพาะ ไม่มีรูปแบบทั่วไป
private static final Singleton _instance;
เช่นกัน
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()
เธรดที่รันลูปอาจไม่เคยเห็นการเปลี่ยนแปลงใด ๆ
สังเกตว่าไม่จำเป็นต้องทำการซิงโครไนซ์
volatile
คำหลักและดูเหมือนว่าจะทำงานได้ดีเสมอ
ตัวอย่างทั่วไปหนึ่งตัวอย่างสำหรับการใช้volatile
คือใช้volatile boolean
ตัวแปรเป็นแฟล็กเพื่อยกเลิกเธรด หากคุณเริ่มเธรดและคุณต้องการให้สามารถขัดจังหวะเธรดอย่างปลอดภัยจากเธรดอื่นคุณสามารถให้เธรดตรวจสอบการตั้งค่าสถานะเป็นระยะ หากต้องการหยุดให้ตั้งค่าสถานะเป็นจริง ด้วยการตั้งค่าสถานะvolatile
คุณสามารถตรวจสอบให้แน่ใจว่าเธรดที่กำลังตรวจสอบนั้นจะเห็นว่ามีการตั้งค่าในครั้งถัดไปที่ตรวจสอบโดยไม่ต้องใช้แม้แต่synchronized
บล็อก
ตัวแปรที่ประกาศพร้อมกับvolatile
คำหลักมีคุณสมบัติหลักสองประการที่ทำให้เป็นพิเศษ
ถ้าเรามีตัวแปรที่ระเหยได้มันจะไม่สามารถแคชลงในหน่วยความจำแคชของไมโครคอมพิวเตอร์ได้ การเข้าถึงเกิดขึ้นเสมอจากหน่วยความจำหลัก
หากมีการดำเนินการเขียนไปในตัวแปรระเหยและทันใดนั้นการดำเนินการอ่านมีการร้องขอจะรับประกันว่าการดำเนินการเขียนจะแล้วเสร็จก่อนที่จะมีการดำเนินการอ่าน
สองคุณสมบัติข้างต้นอนุมานได้ว่า
และในทางกลับกัน
volatile
คำหลักเป็นวิธีที่เหมาะในการรักษาตัวแปรที่ใช้ร่วมกันซึ่งมีจำนวนเธรดเครื่องอ่าน 'n' และมีเพียงหนึ่งเธรดนักเขียนเพื่อเข้าถึง เมื่อเราเพิ่มvolatile
คำหลักเสร็จแล้วก็จะทำ ไม่มีค่าใช้จ่ายอื่นใดเกี่ยวกับความปลอดภัยของด้ายConversly,
เราไม่สามารถทำให้การใช้volatile
คำหลัก แต่เพียงผู้เดียวเพื่อตอบสนองความตัวแปรที่ใช้ร่วมกันซึ่งมีหัวข้อนักเขียนมากกว่าหนึ่งเข้าถึง
ไม่มีใครพูดถึงการรักษาการอ่านและการเขียนสำหรับตัวแปรชนิดยาวและคู่ การอ่านและเขียนเป็นการดำเนินการแบบปรมาณูสำหรับตัวแปรอ้างอิงและสำหรับตัวแปรดั้งเดิมส่วนใหญ่ยกเว้นสำหรับชนิดตัวแปรแบบยาวและแบบคู่ซึ่งต้องใช้คำสำคัญแบบระเหยเพื่อการดำเนินการแบบอะตอมมิก @link
ใช่ระเหยต้องใช้ทุกครั้งที่คุณต้องการให้ตัวแปรหลายตัวแปรสามารถเข้าถึงได้โดยใช้หลายเธรด มันไม่ได้เป็น usecase ที่ใช้กันทั่วไปเพราะโดยทั่วไปแล้วคุณจำเป็นต้องดำเนินการมากกว่าหนึ่งการดำเนินงานของอะตอม (เช่นตรวจสอบสถานะของตัวแปรก่อนที่จะแก้ไข) ซึ่งในกรณีนี้คุณจะต้องใช้บล็อกซิงโครไนซ์แทน
ในความคิดของฉันมีสองสถานการณ์ที่สำคัญอื่น ๆ นอกเหนือจากการหยุดเธรดซึ่งใช้คำหลักระเหยง่ายคือ:
คุณจะต้องใช้คำหลัก 'ระเหย' หรือ 'ทำข้อมูลให้ตรงกัน' และเครื่องมือและเทคนิคการควบคุมภาวะพร้อมกันอื่น ๆ ที่คุณมีเมื่อคุณต้องการพัฒนาแอพพลิเคชั่นแบบมัลติเธรด ตัวอย่างของแอปพลิเคชันดังกล่าวคือแอพเดสก์ท็อป
หากคุณกำลังพัฒนาแอปพลิเคชันที่จะปรับใช้กับแอปพลิเคชันเซิร์ฟเวอร์ (Tomcat, JBoss AS, Glassfish และอื่น ๆ ) คุณไม่จำเป็นต้องจัดการกับการควบคุมภาวะพร้อมกันตามที่ได้รับการจัดการโดยแอปพลิเคชันเซิร์ฟเวอร์ ในความเป็นจริงถ้าฉันจำได้อย่างถูกต้องมาตรฐาน Java EE ห้ามควบคุมการทำงานพร้อมกันใน servlets และ EJBs เพราะมันเป็นส่วนหนึ่งของ 'โครงสร้างพื้นฐาน' เลเยอร์ที่คุณควรจะเป็นอิสระจากการจัดการมัน คุณสามารถควบคุมการทำงานพร้อมกันในแอพดังกล่าวเฉพาะในกรณีที่คุณใช้วัตถุเดี่ยว แม้ว่าคุณจะถักส่วนประกอบของคุณโดยใช้ frameworkd เช่น Spring
ดังนั้นในกรณีส่วนใหญ่ของการพัฒนา Java ที่แอปพลิเคชันเป็นเว็บแอปพลิเคชันและใช้ IoC framework เช่น Spring หรือ EJB คุณไม่จำเป็นต้องใช้ 'volatile'
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);
}
}
}
ใช่ฉันใช้มันค่อนข้างมาก - มันมีประโยชน์มากสำหรับโค้ดแบบมัลติเธรด บทความที่คุณชี้ว่าเป็นบทความที่ดี แม้ว่าจะมีสองสิ่งสำคัญที่ต้องคำนึงถึง:
ทุกเธรดที่เข้าถึงเขตข้อมูลระเหยจะอ่านค่าปัจจุบันก่อนดำเนินการต่อแทนการใช้แคช (อาจมีค่า)
ตัวแปรสมาชิกเท่านั้นที่สามารถเปลี่ยนแปลงได้หรือชั่วคราว
ใช่แน่นอน (และไม่ใช่เฉพาะใน Java แต่ยังอยู่ใน C #) มีบางครั้งที่คุณจำเป็นต้องได้รับหรือตั้งค่าที่รับประกันว่าเป็นการปฏิบัติการแบบปรมาณูบนแพลตฟอร์มที่คุณได้รับตัวอย่างเช่น int หรือบูลีน แต่ไม่ต้องการ ด้านบนของเธรดล็อก คำสำคัญระเหยช่วยให้คุณมั่นใจได้ว่าเมื่อคุณอ่านค่าที่คุณได้รับมูลค่าปัจจุบันและไม่ใช่ค่าแคชที่เพิ่งล้าสมัยโดยการเขียนในหัวข้ออื่น
มีการใช้คำหลักระเหยง่ายสองแบบ
ป้องกัน JVM จากการอ่านค่าในรีจิสเตอร์และบังคับให้ค่าอ่านจากหน่วยความจำ
ธงยุ่งถูกนำมาใช้เพื่อป้องกันไม่ให้ด้ายจากการศึกษาในขณะที่อุปกรณ์ไม่ว่างและธงที่ไม่ได้ป้องกันโดยล็อค:
while (busy) {
/* do something else */
}
เธรดการทดสอบจะดำเนินการต่อเมื่อเธรดอื่นปิดสถานะไม่ว่าง :
busy = 0;
อย่างไรก็ตามเนื่องจากการเข้าถึงไม่ว่างในเธรดการทดสอบบ่อยครั้ง JVM อาจปรับการทดสอบให้เหมาะสมโดยการกำหนดค่าของการยุ่งในการลงทะเบียนจากนั้นทดสอบเนื้อหาของการลงทะเบียนโดยไม่ต้องอ่านค่าของการยุ่งในหน่วยความจำก่อนการทดสอบทุกครั้ง เธรดการทดสอบจะไม่เห็นการเปลี่ยนแปลงที่ยุ่งและเธรดอื่นจะเปลี่ยนค่าของการยุ่งในหน่วยความจำเท่านั้นทำให้เกิดการหยุดชะงัก การประกาศสถานะไม่ว่างเนื่องจากความผันผวนจะบังคับให้อ่านค่าก่อนการทดสอบแต่ละครั้ง
ลดความเสี่ยงของข้อผิดพลาดความสอดคล้องของหน่วยความจำ
การใช้ตัวแปรระเหยช่วยลดความเสี่ยงของข้อผิดพลาดความสอดคล้องของหน่วยความจำเพราะการเขียนตัวแปรแปรปรวนใด ๆ จะสร้าง "เกิดขึ้นก่อน"ความสัมพันธ์กับการอ่านของตัวแปรเดียวกันที่ตามมา ซึ่งหมายความว่าการเปลี่ยนแปลงของตัวแปรที่เปลี่ยนแปลงได้จะเกิดขึ้นกับเธรดอื่น ๆ
เทคนิคการอ่านการเขียนโดยไม่มีข้อผิดพลาดความสอดคล้องของหน่วยความจำเรียกว่าการกระทำของอะตอมการกระทำของอะตอม
การกระทำของอะตอมเป็นสิ่งที่เกิดขึ้นอย่างมีประสิทธิภาพในคราวเดียว การกระทำของอะตอมไม่สามารถหยุดอยู่ตรงกลาง: มันเกิดขึ้นอย่างสมบูรณ์หรือไม่เกิดขึ้นเลย ไม่มีผลข้างเคียงของการกระทำของอะตอมจนกว่าจะเห็นการกระทำเสร็จสมบูรณ์
ด้านล่างนี้คือการกระทำที่คุณสามารถระบุได้ว่าเป็นอะตอมมิก:
ไชโย!
volatile
สำหรับโปรแกรมเมอร์ว่าค่านั้นจะเป็นปัจจุบันเสมอ ปัญหาคือค่าที่สามารถบันทึกในหน่วยความจำฮาร์ดแวร์ชนิดต่าง ๆ ตัวอย่างเช่นสามารถลงทะเบียน CPU, แคช CPU, RAM ... СPUลงทะเบียนและแคช CPU เป็นของ CPU และไม่สามารถแบ่งปันข้อมูลที่แตกต่างจาก RAM ที่อยู่ในการช่วยเหลือในการใช้พลังงานหลายเธรด
volatile
keyword ระบุว่าตัวแปรจะถูกอ่านและเขียนจาก / ถึงหน่วยความจำ RAM โดยตรงโดยตรงมันมีร่องรอยการคำนวณบางอย่าง
Java 5
ขยายvolatile
โดยการสนับสนุนhappens-before
[เกี่ยวกับ]
การเขียนไปยังเขตข้อมูลระเหยเกิดขึ้นก่อนที่จะอ่านทุกครั้งของสนามที่ตามมา
volatile
คำหลักที่ไม่ได้รักษาrace condition
สถานการณ์เมื่อหลายหัวข้อสามารถเขียนค่าบางอย่างไปพร้อม ๆ กัน คำตอบคือsynchronized
คำสำคัญ[เกี่ยวกับ]
ดังนั้นความปลอดภัยก็ต่อเมื่อหนึ่งเธรดที่เขียนและอื่น ๆ เพียงแค่อ่านvolatile
ค่า
สารระเหยทำตาม
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
จากหน้าเอกสารของ oracle ความต้องการตัวแปรระเหยเกิดขึ้นเพื่อแก้ไขปัญหาความสอดคล้องของหน่วยความจำ:
การใช้ตัวแปรระเหยช่วยลดความเสี่ยงของข้อผิดพลาดความสอดคล้องของหน่วยความจำเพราะการเขียนใด ๆ ไปยังตัวแปรระเหยสร้างความสัมพันธ์ที่เกิดขึ้นก่อนที่จะมีการอ่านตัวแปรเดียวกันที่ตามมา
ซึ่งหมายความว่าการเปลี่ยนแปลงvolatile
ตัวแปรสามารถมองเห็นกระทู้อื่น ๆ ได้เสมอ นอกจากนี้ยังหมายความว่าเมื่อเธรดอ่านตัวแปรระเหยมันไม่เพียง แต่เห็นการเปลี่ยนแปลงล่าสุดไปยังตัวแปรvolatile
แต่ยังมีผลข้างเคียงของโค้ดที่นำไปสู่การเปลี่ยนแปลง
ตามที่อธิบายในPeter Parker
คำตอบหากไม่มีvolatile
ตัวแก้ไขสแต็กของแต่ละเธรดอาจมีสำเนาของตัวแปรของตัวเอง โดยทำให้ตัวแปรเป็นvolatile
ปัญหาความสอดคล้องของหน่วยความจำได้รับการแก้ไข
ดูที่หน้าการสอนของjenkovเพื่อความเข้าใจที่ดีขึ้น
ดูคำถาม SE ที่เกี่ยวข้องเพื่อดูรายละเอียดเพิ่มเติมเกี่ยวกับการระเหย & การใช้เคสเพื่อการระเหย:
ความแตกต่างระหว่างสารระเหยและซิงโครไนซ์ใน Java
กรณีใช้งานจริงหนึ่งกรณี:
java.text.SimpleDateFormat("HH-mm-ss")
คุณมีกระทู้จำนวนมากซึ่งต้องพิมพ์เวลาปัจจุบันในรูปแบบเฉพาะตัวอย่างเช่น: Yon สามารถมีคลาสได้หนึ่งคลาสซึ่งแปลงเวลาปัจจุบันเป็นSimpleDateFormat
และอัปเดตตัวแปรสำหรับทุก ๆ หนึ่งวินาที หัวข้ออื่น ๆ ทั้งหมดสามารถใช้ตัวแปรระเหยนี้เพื่อพิมพ์เวลาปัจจุบันในล็อกไฟล์
ตัวแปรระเหยเป็นการซิงโครไนซ์น้ำหนักเบา เมื่อการเปิดเผยข้อมูลล่าสุดในบรรดากระทู้ทั้งหมดเป็นสิ่งที่ต้องการและอะตอมมิกซิตี้สามารถถูกบุกรุกได้ในสถานการณ์เช่นนี้จำเป็นต้องมีตัวแปรที่เปลี่ยนแปลงได้ อ่านกับตัวแปรที่ลบเลือนได้เสมอการเขียนล่าสุดที่ทำโดยเธรดใด ๆ เนื่องจากไม่มีการแคชในรีจิสเตอร์และแคชที่โปรเซสเซอร์อื่นไม่สามารถมองเห็นได้ ระเหยง่ายคือล็อคฟรี ฉันใช้สารระเหยเมื่อสถานการณ์ตรงตามเกณฑ์ที่กล่าวไว้ข้างต้น
ปุ่มระเหยเมื่อใช้กับตัวแปรจะทำให้แน่ใจว่าเธรดที่อ่านตัวแปรนี้จะเห็นค่าเดียวกัน ตอนนี้ถ้าคุณมีหลายเธรดที่อ่านและเขียนไปยังตัวแปรการทำให้ตัวแปรระเหยไม่เพียงพอและข้อมูลจะเสียหาย เธรดรูปภาพได้อ่านค่าเดียวกัน แต่แต่ละอันได้ทำบาง chages (พูดเพิ่มค่าตัวนับ) เมื่อเขียนกลับไปที่หน่วยความจำความสมบูรณ์ของข้อมูลถูกละเมิด นั่นคือเหตุผลที่จำเป็นต้องทำการซิงโครไนซ์ varible (วิธีที่แตกต่างเป็นไปได้)
ถ้าการเปลี่ยนแปลงเกิดขึ้น 1 เธรดและสิ่งอื่น ๆ เพียงแค่อ่านค่านี้ความผันผวนจะเหมาะสม
โดยทั่วไปแล้วตัวแปรระเหยจะใช้สำหรับการอัปเดตทันที (ล้างข้อมูล) ในบรรทัดแคชที่ใช้ร่วมกันหลักเมื่อมีการอัปเดตเพื่อให้การเปลี่ยนแปลงสะท้อนไปที่เธรดผู้ทำงานทั้งหมดทันที
ด้านล่างเป็นรหัสที่ง่ายมากที่จะแสดงให้เห็นถึงความต้องการของ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
volatile
ที่มาพร้อมกับ Java Memory Model ใหม่ที่กำหนดใน JSR 133: เมื่อเธรดอ่านvolatile
ตัวแปรมันจะเห็นเฉพาะค่าที่เขียนไปล่าสุดโดยเธรดอื่น แต่ยังเขียนอื่น ๆ ไปยังตัวแปรอื่นที่ สามารถมองเห็นได้ในเธรดอื่นในขณะที่volatile
เขียน ดูคำตอบนี้และข้อมูลอ้างอิงนี้