Chúng ta có nên khai báo các trường riêng tư là volatile
nếu instanced được sử dụng trong nhiều luồng không?java: các trường riêng tư `dễ bay hơi với getters và setters
Trong Effective Java, có một ví dụ trong đó mã số không làm việc mà không dễ bay hơi:
import java.util.concurrent.TimeUnit;
// Broken! - How long would you expect this program to run?
public class StopThread {
private static boolean stopRequested; // works, if volatile is here
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
Các giải thích nói rằng
while(!stopRequested)
i++;
được tối ưu hóa để một cái gì đó như thế này:
if(!stopRequested)
while(true)
i++;
sửa đổi thêm nữa của stopRequested
không được nhìn thấy theo chủ đề nền, vì vậy nó lặp đi lặp lại mãi mãi. (BTW, mã mà chấm dứt mà không volatile
trên JRE7.)
Bây giờ xem xét lớp này:
public class Bean {
private boolean field = true;
public boolean getField() {
return field;
}
public void setField(boolean value) {
field = value;
}
}
và một sợi như sau:
public class Worker implements Runnable {
private Bean b;
public Worker(Bean b) {
this.b = b;
}
@Override
public void run() {
while(b.getField()) {
System.err.println("Waiting...");
try { Thread.sleep(1000); }
catch(InterruptedException ie) { return; }
}
}
}
Đoạn mã trên làm việc như mong đợi mà không sử dụng các chất bay hơi :
public class VolatileTest {
public static void main(String [] args) throws Exception {
Bean b = new Bean();
Thread t = new Thread(new Worker(b));
t.start();
Thread.sleep(3000);
b.setField(false); // stops the child thread
System.err.println("Waiting the child thread to quit");
t.join();
// if the code gets, here the child thread is stopped
// and it really gets, with JRE7, 6 with -server, -client
}
}
Tôi nghĩ vì công khai setter, c ompiler/JVM không bao giờ nên tối ưu hóa mã gọi getField()
, nhưng this article nói rằng có một số mẫu "Volatile Bean" (Mẫu # 4), nên được áp dụng để tạo các lớp an toàn có thể thay đổi được. Cập nhật: có lẽ bài viết đó chỉ áp dụng cho IBM JVM?
Câu hỏi đặt ra là: phần nào của JLS nói rõ ràng hoặc ngầm định rằng các trường nguyên thủy riêng với getters/setters công khai phải được khai báo là volatile
(hoặc chúng không phải)?
Xin lỗi một câu hỏi dài, tôi đã cố gắng giải thích sự cố chi tiết. Hãy cho tôi biết nếu có điều gì đó không rõ ràng. Cảm ơn.
không giống như bạn cần trường đó để hủy chuỗi, bạn có thể sử dụng cờ bị gián đoạn trên chuỗi. –
@NathanHughes, các lớp này chỉ là các ví dụ tối thiểu, mã thực tế là khác nhau, không có sự gián đoạn luồng là cần thiết ở đó. – khachik