AtomicBoolean làm gì mà một boolean dễ bay hơi không thể đạt được?
AtomicBoolean làm gì mà một boolean dễ bay hơi không thể đạt được?
Câu trả lời:
Họ chỉ hoàn toàn khác nhau. Xem xét ví dụ này về một volatile
số nguyên:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Nếu hai luồng gọi hàm đồng thời, i
có thể là 5 sau đó, vì mã được biên dịch sẽ hơi giống với điều này (ngoại trừ bạn không thể đồng bộ hóa trên int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Nếu một biến là dễ bay hơi, mọi truy cập nguyên tử vào nó đều được đồng bộ hóa, nhưng không phải lúc nào cũng rõ ràng những gì thực sự đủ điều kiện là truy cập nguyên tử. Với một Atomic*
đối tượng, đảm bảo rằng mọi phương thức đều là "nguyên tử".
Vì vậy, nếu bạn sử dụng một AtomicInteger
và getAndAdd(int delta)
, bạn có thể chắc chắn rằng kết quả sẽ có 10
. Theo cùng một cách, nếu cả hai luồng đều boolean
đồng thời phủ định một biến, với một AtomicBoolean
bạn có thể chắc chắn rằng nó có giá trị ban đầu sau đó, với a volatile boolean
, bạn không thể.
Vì vậy, bất cứ khi nào bạn có nhiều hơn một luồng sửa đổi một trường, bạn cần biến nó thành nguyên tử hoặc sử dụng đồng bộ hóa rõ ràng.
Mục đích của volatile
một là khác nhau. Xem xét ví dụ này
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Nếu bạn có một luồng đang chạy loop()
và một luồng gọi khác stop()
, bạn có thể chạy vào một vòng lặp vô hạn nếu bạn bỏ qua volatile
, vì luồng đầu tiên có thể lưu trữ giá trị của điểm dừng. Ở đây, volatile
phục vụ như là một gợi ý cho trình biên dịch để cẩn thận hơn một chút với tối ưu hóa.
volatile
. Câu hỏi đặt ra là về volatile boolean
vs AtomicBoolean
.
volatile boolean
đủ. Nếu cũng có nhiều nhà văn, bạn có thể cần AtomicBoolean
.
Tôi sử dụng các trường dễ bay hơi khi trường được nói CHỈ ĐƯỢC CẬP NHẬT bởi luồng chủ sở hữu của nó và giá trị chỉ được đọc bởi các luồng khác, bạn có thể nghĩ đó là một kịch bản xuất bản / đăng ký trong đó có nhiều người quan sát nhưng chỉ có một nhà xuất bản. Tuy nhiên, nếu những người quan sát đó phải thực hiện một số logic dựa trên giá trị của trường và sau đó đẩy lùi một giá trị mới thì tôi sẽ sử dụng các vars hoặc khóa nguyên tử hoặc các khối được đồng bộ hóa, bất cứ điều gì phù hợp nhất với tôi. Trong nhiều kịch bản đồng thời, nó sôi sục để lấy giá trị, so sánh nó với một kịch bản khác và cập nhật nếu cần, do đó các phương thức so sánhAndset và getAndset có trong các lớp Nguyên tử *.
Kiểm tra JavaDocs của gói java.util.concản.atomic để biết danh sách các lớp Nguyên tử và giải thích tuyệt vời về cách chúng hoạt động (chỉ cần biết rằng chúng không bị khóa, vì vậy chúng có lợi thế hơn về khóa hoặc khối được đồng bộ hóa)
boolean
var, chúng ta nên chọn volatile boolean
.
Bạn không thể làm compareAndSet
, getAndSet
như hoạt động nguyên tử với boolean dễ bay hơi (trừ khi tất nhiên bạn đồng bộ hóa nó).
AtomicBoolean
có các phương thức thực hiện các hoạt động hỗn hợp của chúng một cách nguyên tử và không phải sử dụng một synchronized
khối. Mặt khác, volatile boolean
chỉ có thể thực hiện các thao tác ghép nếu được thực hiện trong một synchronized
khối.
Các hiệu ứng bộ nhớ của việc đọc / ghi volatile boolean
giống hệt với get
và set
các phương thức AtomicBoolean
tương ứng.
Ví dụ, compareAndSet
phương thức sẽ thực hiện một cách cơ bản như sau (không có synchronized
khối):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Do đó, compareAndSet
phương thức sẽ cho phép bạn viết mã được đảm bảo chỉ thực hiện một lần, ngay cả khi được gọi từ nhiều luồng. Ví dụ:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Được đảm bảo chỉ thông báo cho người nghe một lần (giả sử không có luồng nào khác đặt AtomicBoolean
ngược false
lại sau khi được đặt thành true
).
volatile
đảm bảo từ khóa xảy ra - trước khi mối quan hệ giữa các chủ đề chia sẻ biến đó. Nó không đảm bảo với bạn rằng 2 hoặc nhiều luồng sẽ không làm gián đoạn lẫn nhau trong khi truy cập biến boolean đó.
Atomic*
lớp kết thúc một volatile
lĩnh vực.
Boolean dễ bay hơi so với AtomicBoolean
Các lớp nguyên tử * bao bọc một nguyên thủy dễ bay hơi cùng loại. Từ nguồn:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Vì vậy, nếu tất cả những gì bạn đang làm là nhận và thiết lập một Nguyên tử * thì bạn cũng có thể chỉ cần có một trường biến động.
AtomicBoolean làm gì mà một boolean dễ bay hơi không thể đạt được?
Các lớp nguyên tử * cung cấp cho bạn các phương thức cung cấp chức năng nâng cao hơn, chẳng hạn như incrementAndGet()
, compareAndSet()
và các phương thức khác thực hiện nhiều thao tác (get / tăng / set, test / set) mà không khóa. Đó là lý do tại sao các lớp nguyên tử * rất mạnh.
Ví dụ: nếu nhiều luồng đang sử dụng đoạn mã sau bằng cách sử dụng ++
, sẽ có các điều kiện chạy đua vì ++
thực tế là: get, tăng và đặt.
private volatile value;
...
// race conditions here
value++;
Tuy nhiên, đoạn mã sau sẽ hoạt động trong môi trường đa luồng một cách an toàn mà không cần khóa:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
Cũng cần lưu ý rằng việc bọc trường dễ bay hơi của bạn bằng lớp Nguyên tử * là một cách tốt để đóng gói tài nguyên được chia sẻ quan trọng từ quan điểm đối tượng. Điều này có nghĩa là các nhà phát triển không thể chỉ đối phó với trường giả định rằng nó không được chia sẻ có thể gây ra sự cố với trường ++; hoặc mã khác giới thiệu điều kiện chủng tộc.
Nếu có nhiều luồng truy cập biến cấp độ lớp thì mỗi luồng có thể giữ bản sao của biến đó trong bộ đệm của luồng.
Làm cho biến dễ bay hơi sẽ ngăn các luồng giữ bản sao của biến trong bộ đệm của luồng.
Các biến nguyên tử là khác nhau và chúng cho phép sửa đổi nguyên tử các giá trị của chúng.
Kiểu nguyên thủy Boolean là nguyên tử cho các hoạt động ghi và đọc, dễ bay hơi đảm bảo nguyên tắc xảy ra trước khi xảy ra. Vì vậy, nếu bạn cần một get () và set () đơn giản thì bạn không cần AtomicBoolean.
Mặt khác, nếu bạn cần thực hiện một số kiểm tra trước khi đặt giá trị của biến, ví dụ "nếu đúng thì đặt thành sai", thì trong trường hợp này, bạn cũng cần thực hiện thao tác này một cách nguyên tử, trong trường hợp này, hãy sử dụng so sánh và các phương thức khác được cung cấp bởi AtomicBoolean, vì nếu bạn cố gắng thực hiện logic này với boolean dễ bay hơi, bạn sẽ cần một số đồng bộ hóa để đảm bảo rằng giá trị không thay đổi giữa get và set.
Ghi nhớ IDIOM -
ĐỌC - MODIFY- VIẾT điều này bạn không thể đạt được với biến động
volatile
chỉ hoạt động trong trường hợp luồng chủ sở hữu có khả năng cập nhật giá trị trường và các luồng khác chỉ có thể đọc.
Nếu bạn chỉ có một luồng sửa đổi boolean của mình, bạn có thể sử dụng một boolean dễ bay hơi (thông thường bạn làm điều này để xác định một stop
biến được kiểm tra trong vòng lặp chính của luồng).
Tuy nhiên, nếu bạn có nhiều luồng sửa đổi boolean, bạn nên sử dụng một AtomicBoolean
. Khác, mã sau đây không an toàn:
boolean r = !myVolatileBoolean;
Thao tác này được thực hiện theo hai bước:
Nếu một luồng khác sửa đổi giá trị giữa #1
và 2#
, bạn có thể có kết quả sai. AtomicBoolean
phương pháp tránh vấn đề này bằng cách thực hiện các bước #1
và #2
nguyên tử.
Cả hai đều có cùng một khái niệm nhưng trong boolean nguyên tử, nó sẽ cung cấp tính nguyên tử cho hoạt động trong trường hợp chuyển đổi cpu xảy ra ở giữa.