Boolean dễ bay hơi so với AtomicBoolean


244

AtomicBoolean làm gì mà một boolean dễ bay hơi không thể đạt được?


16
Tôi đang tìm kiếm một câu trả lời nhiều sắc thái hơn cho: "những hạn chế của mỗi loại là gì?". Chẳng hạn, nếu là một cờ được đặt bởi một luồng và được đọc bởi một hoặc nhiều người khác, thì không cần phải có AtomicBoolean. Tuy nhiên, như tôi đang thấy với những câu trả lời này, nếu luồng đang chia sẻ một biến trong nhiều luồng có thể viết và đang hoạt động dựa trên kết quả của các lần đọc của chúng, thì AtomicBoolean đưa các hoạt động không khóa kiểu CAS vào hoạt động. Tôi đang học khá nhiều ở đây, thực sự. Hy vọng, những người khác cũng sẽ được hưởng lợi.
JeffV

1
bản sao có thể có của boolean dễ bay hơi so với AtomicBoolean
Flow

boolean dễ bay hơi sẽ cần đồng bộ hóa rõ ràng để xử lý các điều kiện chủng tộc, nói cách khác, kịch bản như tài nguyên được chia sẻ được cập nhật (thay đổi trạng thái) bởi nhiều luồng, ví dụ như bộ đếm tăng / giảm hoặc lật boolean.
sactiw

Câu trả lời:


99

Họ chỉ hoàn toàn khác nhau. Xem xét ví dụ này về một volatilesố nguyên:

volatile int i = 0;
void incIBy5() {
    i += 5;
}

Nếu hai luồng gọi hàm đồng thời, icó 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 AtomicIntegergetAndAdd(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 AtomicBooleanbạ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 volatilemộ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, volatilephụ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.


84
-1: bạn đang đưa ra ví dụ nhưng không thực sự giải thích sự khác biệt giữa một biến động và một nguyên tử.
Jason S

70
Câu hỏi không phải là về volatile. Câu hỏi đặt ra là về volatile booleanvs AtomicBoolean.
heo

26
-1: câu hỏi được hỏi cụ thể về boolean, đây là trường hợp duy nhất so với các loại dữ liệu khác và cần được giải thích trực tiếp.
John Haager

8
@ sgp15 Nó phải thực hiện với đồng bộ hóa như Java 5.
Man of One Way

6
Nếu giá trị boolean được đọc bởi nhiều luồng, nhưng chỉ được viết bởi một luồng, thì volatile booleanđủ. Nếu cũng có nhiều nhà văn, bạn có thể cần AtomicBoolean.
StvnBrkdll

263

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)


1
@ksl Tôi nghĩ @teto muốn mô tả rằng nếu chỉ có một luồng sửa đổi booleanvar, chúng ta nên chọn volatile boolean.
znlyj

2
Tóm tắt tuyệt vời.
Ravindra babu

56

Bạn không thể làm compareAndSet, getAndSetnhư 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ó).


6
Điều này là đúng, nhưng đây có phải là một yêu cầu khá hiếm đối với một boolean không?
Robin

1
@Robin nghĩ về việc sử dụng nó để kiểm soát một cách gọi lười biếng của một phương thức khởi tạo.
Ustaman Sangat

Trên thực tế tôi sẽ nghĩ rằng đây là một trong những trường hợp sử dụng chính.
lừa4jesus

42

AtomicBooleancó 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 synchronizedkhối. Mặt khác, volatile booleanchỉ có thể thực hiện các thao tác ghép nếu được thực hiện trong một synchronizedkhối.

Các hiệu ứng bộ nhớ của việc đọc / ghi volatile booleangiống hệt với getsetcác phương thức AtomicBooleantương ứng.

Ví dụ, compareAndSetphương thức sẽ thực hiện một cách cơ bản như sau (không có synchronizedkhối):

if (value == expectedValue) {
    value = newValue;
    return true;
} else {
    return false;
}

Do đó, compareAndSetphươ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 AtomicBooleanngược falselại sau khi được đặt thành true).


14

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 đó.


14
Truy cập Boolean (như kiểu nguyên thủy) là nguyên tử trong Java. Cả đọc và bài tập. Vì vậy, không có luồng nào khác sẽ "làm gián đoạn" các hoạt động boolean.
Maciej Biłas

1
Tôi xin lỗi nhưng làm thế nào để trả lời câu hỏi này? Một Atomic*lớp kết thúc một volatilelĩnh vực.
Xám

CPU không lưu trữ yếu tố chính để thiết lập không ổn định? Để đảm bảo rằng giá trị đọc thực sự là giá trị được đặt thành gần đây nhất
vui vẻ vào

8

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.


5

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.


4

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.


3

Ghi nhớ IDIOM -

ĐỌC - MODIFY- VIẾT điều này bạn không thể đạt được với biến động


2
Ngắn, giòn và cho điểm. volatilechỉ 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.
Chaklader Asfak Arefe

3

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 stopbiế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:

  1. Giá trị boolean được đọc.
  2. Giá trị boolean được viết.

Nếu một luồng khác sửa đổi giá trị giữa #12#, bạn có thể có kết quả sai. AtomicBooleanphương pháp tránh vấn đề này bằng cách thực hiện các bước #1#2nguyên tử.


-1

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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.