Dễ bay hơi so với tĩnh trong Java


265

Có đúng không khi nói điều đó staticcó nghĩa là một bản sao của giá trị cho tất cả các đối tượng và volatilecó nghĩa là một bản sao của giá trị cho tất cả các luồng?

Dù sao, một staticgiá trị biến cũng sẽ là một giá trị cho tất cả các luồng, vậy tại sao chúng ta nên đi volatile?


Giải thích chính thức về tính không ổn định: cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#voliverse
Vadzim

Câu trả lời:


365

Khai báo một biến tĩnh trong Java, có nghĩa là sẽ chỉ có một bản sao, bất kể có bao nhiêu đối tượng của lớp được tạo. Biến sẽ có thể truy cập được ngay cả khi không có Objectstạo. Tuy nhiên, các chủ đề có thể có các giá trị lưu trữ cục bộ của nó.

Khi một biến là biến động và không tĩnh , sẽ có một biến cho mỗi biến Object. Vì vậy, trên bề mặt có vẻ như không có sự khác biệt so với một biến thông thường nhưng hoàn toàn khác với tĩnh . Tuy nhiên, ngay cả với Objectcác trường, một luồng có thể lưu trữ giá trị biến cục bộ.

Điều này có nghĩa là nếu hai luồng cập nhật đồng thời một biến của cùng một đối tượng và biến đó không được khai báo là không ổn định, có thể xảy ra trường hợp một trong các luồng có trong bộ đệm có giá trị cũ.

Ngay cả khi bạn truy cập một giá trị tĩnh thông qua nhiều luồng, mỗi luồng có thể có bản sao lưu trong bộ nhớ cache cục bộ của nó! Để tránh điều này, bạn có thể khai báo biến là biến động tĩnh và điều này sẽ buộc luồng phải đọc mỗi lần giá trị toàn cục.

Tuy nhiên, không ổn định không phải là một thay thế cho đồng bộ hóa thích hợp!
Ví dụ:

private static volatile int counter = 0;

private void concurrentMethodWrong() {
  counter = counter + 5;
  //do something
  counter = counter - 5;
}

Thực hiện concurrentMethodWrongđồng thời nhiều lần có thể dẫn đến giá trị cuối cùng của bộ đếm khác 0!
Để giải quyết vấn đề, bạn phải thực hiện khóa:

private static final Object counterLock = new Object();

private static volatile int counter = 0;

private void concurrentMethodRight() {
  synchronized (counterLock) {
    counter = counter + 5;
  }
  //do something
  synchronized (counterLock) {
    counter = counter - 5;
  }
}

Hoặc sử dụng AtomicIntegerlớp học.


7
Công cụ sửa đổi dễ bay hơi đảm bảo rằng bất kỳ luồng nào đọc trường sẽ thấy giá trị được viết gần đây nhất, do đó, cần thiết nếu biến được chia sẻ giữa nhiều luồng và bạn cần tính năng này, tùy thuộc vào trường hợp sử dụng của bạn.
stivlo

5
Bộ nhớ cache khi bạn nói "bộ nhớ cache cục bộ" là gì? Bộ đệm CPU, một số loại bộ đệm JVM?
mert inan

6
@mertinan có, biến có thể nằm trong bộ đệm gần bộ xử lý hoặc lõi. Xem cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html để biết thêm chi tiết.
stivlo

15
'dễ bay hơi' không ngụ ý 'một biến cho mỗi đối tượng'. Sự vắng mặt của 'tĩnh' làm điều đó. -1 vì đã không làm sáng tỏ quan niệm sai lầm cơ bản này về phía OP.
Hầu tước Lorne

27
@EJP Tôi nghĩ rằng câu "Khai báo một biến là không ổn định, sẽ có một biến cho mỗi Đối tượng. Vì vậy, trên bề mặt có vẻ như không có sự khác biệt so với một biến thông thường" đã giải thích rằng, tôi đã thêm và không tĩnh , vui lòng chỉnh sửa bài viết và cải thiện từ ngữ để làm cho nó rõ ràng hơn.
stivlo

288

Sự khác biệt giữa tĩnh và dễ bay hơi:

Biến tĩnh : Nếu hai Chủ đề (giả sử t1t2) đang truy cập vào cùng một đối tượng và cập nhật một biến được khai báo là static thì nó có nghĩa t1t2có thể làm cho bản sao cục bộ của riêng của họ về cùng một đối tượng (trong đó có các biến tĩnh) trong bộ nhớ cache của mình, vì vậy cập nhật được tạo bởi t1biến tĩnh trong bộ đệm cục bộ của nó sẽ không phản ánh trong biến tĩnh cho t2bộ đệm.

Các biến tĩnh được sử dụng trong ngữ cảnh của Object nơi cập nhật được tạo bởi một đối tượng sẽ phản ánh trong tất cả các đối tượng khác của cùng một lớp nhưng không phải trong ngữ cảnh của Thread khi cập nhật một luồng cho biến tĩnh sẽ phản ánh các thay đổi ngay lập tức cho tất cả chủ đề (trong bộ nhớ cache cục bộ của họ).

Biến dễ bay hơi : Nếu hai Chủ đề (giả sử t1t2) đang truy cập vào cùng một đối tượng và cập nhật một biến được khai báo là dễ bay hơi thì điều đó có nghĩa t1t2có thể tạo bộ đệm cục bộ của Đối tượng ngoại trừ biến được khai báo là biến động . Vì vậy, biến dễ bay hơi sẽ chỉ có một bản sao chính sẽ được cập nhật bởi các luồng khác nhau và cập nhật được tạo bởi một luồng cho biến dễ bay hơi sẽ ngay lập tức phản ánh đến Chủ đề khác.


6
Xin chào @Som, Vui lòng sửa cho tôi nếu tôi sai. Nhưng bạn không nghĩ câu lệnh " nhưng không phải trong ngữ cảnh của Thread khi cập nhật một luồng cho biến tĩnh sẽ phản ánh các thay đổi ngay lập tức cho tất cả các luồng (trong bộ đệm cục bộ của chúng). " Nên "nhưng không phải trong ngữ cảnh của Chủ đề trong đó việc cập nhật một luồng thành biến tĩnh sẽ << KHÔNG >> phản ánh các thay đổi ngay lập tức cho tất cả các luồng (trong bộ đệm cục bộ của chúng). "
Jaikrat

@Jaikrat Vâng, điều đó rất khó hiểu đối với tôi. Tôi hiểu rằng bạn đúng và câu trả lời này sai, như đã viết. Tôi cũng muốn được sửa chữa nếu tôi sai.
stuart

@Jaikrat Chủ đề không lưu trữ các biến tĩnh mặc dù, nhưng hãy tham khảo các biến tĩnh được cập nhật.
Som

@Som Sau đó, bạn muốn sửa lỗi para và xóa nhưng không phải trong ngữ cảnh của Thread . Đó là rất khó hiểu. Cảm ơn
Jaikrat

Đáng buồn thay, câu trả lời này là không chính xác. Trên các CPU hiện đại, thậm chí một volatilebiến có thể được chia sẻ giữa các bộ đệm CPU riêng biệt. Điều này không có vấn đề gì vì bộ đệm đã thương lượng quyền sở hữu độc quyền của dòng bộ đệm trước khi sửa đổi nó.
David Schwartz

32

Ngoài các câu trả lời khác, tôi muốn thêm một hình ảnh cho nó (pic làm cho dễ hiểu)

nhập mô tả hình ảnh ở đây

staticcác biến có thể được lưu trữ cho các chủ đề riêng lẻ. Trong môi trường đa luồng nếu một luồng sửa đổi dữ liệu được lưu trong bộ nhớ cache của nó, điều đó có thể không phản ánh cho các luồng khác vì chúng có một bản sao của nó.

volatilekhai báo đảm bảo rằng các luồng sẽ không lưu trữ dữ liệu và chỉ sử dụng bản sao được chia sẻ .

nguồn hình ảnh


1
biến tĩnh được chia sẻ giữa các đối tượng dưới một chủ đề? Điều này nên đọc các biến tĩnh được chia sẻ giữa các đối tượng tất cả các đối tượng bất kể chủ đề.
cquezel

1
"các biến dễ bay hơi được chia sẻ giữa nhiều luồng (vì vậy các đối tượng cũng vậy)." Biến động không thay đổi cách các biến được chia sẻ giữa nhiều luồng hoặc đối tượng. Nó thay đổi cách thời gian chạy được phép lưu trữ giá trị.
cquezel

1
Nhận xét của bạn về các biến tĩnh cũng áp dụng cho không tĩnh và "sẽ được lưu trong bộ nhớ cache" và "sẽ không phản ánh" có thể được ghi lại "có thể được lưu vào bộ nhớ cache" và "có thể không phản ánh".
cquezel

4
Tôi đã rất bối rối. bức ảnh này đã xóa tất cả các câu hỏi của tôi!
vins

5

Tôi nghĩ staticvolatilekhông có mối quan hệ nào cả. Tôi đề nghị bạn đọc hướng dẫn java để hiểu Truy cập nguyên tử và tại sao sử dụng quyền truy cập nguyên tử, hiểu nội dung xen kẽ , bạn sẽ tìm thấy câu trả lời.


4

Nói một cách đơn giản,

  1. static : staticcác biến được liên kết với lớp , chứ không phải với bất kỳ đối tượng nào . Mỗi phiên bản của lớp chia sẻ một biến lớp, nằm ở một vị trí cố định trong bộ nhớ

  2. dễ bay hơi : Từ khóa này được áp dụng cho cả lớpdụ các biến.

Sử dụng các biến dễ bay hơi sẽ giảm nguy cơ lỗi nhất quán bộ nhớ, bởi vì bất kỳ ghi vào biến dễ bay nào đều thiết lập mối quan hệ xảy ra trước khi đọc các biến tiếp theo của cùng biến đó. Điều này có nghĩa là những thay đổi đối với một biến dễ bay hơi luôn được hiển thị cho các luồng khác

Hãy xem bài viết này bằng cách Javin Paul hiểu các biến dễ bay hơi một cách tốt hơn.

nhập mô tả hình ảnh ở đây

Nếu không có volatiletừ khóa, giá trị của biến trong ngăn xếp của mỗi luồng có thể khác nhau. Bằng cách biến biến thành volatile, tất cả các luồng sẽ nhận được cùng một giá trị trong bộ nhớ làm việc và các lỗi nhất quán bộ nhớ đã được tránh.

Ở đây thuật ngữ variablecó thể là biến static(lớp) hoặc biến instance(đối tượng).

Về truy vấn của bạn:

Dù sao, một giá trị biến tĩnh cũng sẽ là một giá trị cho tất cả các luồng, vậy tại sao chúng ta phải đi cho biến động?

Nếu tôi cần instancebiến trong ứng dụng của mình, tôi không thể sử dụng staticbiến. Ngay cả trong trường hợp staticbiến, tính nhất quán không được đảm bảo do bộ đệm Thread như trong sơ đồ.

Sử dụng volatilecác biến làm giảm nguy cơ lỗi nhất quán bộ nhớ, bởi vì bất kỳ ghi vào biến dễ bay hơi nào cũng thiết lập mối quan hệ xảy ra trước khi đọc các biến tiếp theo của cùng biến đó. Điều này có nghĩa là những thay đổi đối với một biến dễ bay hơi luôn được hiển thị cho các luồng khác.

Hơn nữa, điều đó cũng có nghĩa là khi một luồng đọc một biến dễ bay hơi, nó không chỉ thấy sự thay đổi mới nhất đối với biến động, mà cả các tác dụng phụ của mã dẫn đến thay đổi => lỗi nhất quán bộ nhớ vẫn có thể xảy ra với các biến dễ bay hơi . Để tránh tác dụng phụ, bạn phải sử dụng các biến được đồng bộ hóa. Nhưng có một giải pháp tốt hơn trong java.

Sử dụng truy cập biến nguyên tử đơn giản hiệu quả hơn so với truy cập các biến này thông qua mã được đồng bộ hóa

Một số lớp trong java.util.concurrentgói cung cấp các phương thức nguyên tử không dựa vào đồng bộ hóa.

Tham khảo bài viết kiểm soát đồng thời mức cao này để biết thêm chi tiết.

Đặc biệt có một cái nhìn vào các biến nguyên tử .

Câu hỏi SE liên quan:

Nguyên tử Vs dễ bay hơi

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

Sự khác biệt giữa dễ bay hơi và đồng bộ hóa trong Java


Tôi thực sự đánh giá cao câu trả lời này. Tôi biết những gì volatiletrước đó là gì , nhưng, câu trả lời này làm rõ rất nhiều cho tôi tại sao tôi vẫn cần sử dụng volatilevới staticbiến.
Chaklader Asfak Arefe

dễ bay hơi: Từ khóa này có thể áp dụng cho cả biến lớp và biến thể. Tuyên bố bạn nói ở trên là không chính xác liên quan đến áp dụng cho lớp. chỉ có hai từ khóa áp dụng cho biến là biến động và nhất thời. rất dễ bay hơi sẽ không áp dụng cho lớp học
ASR

dễ bay hơi được áp dụng cho các biến lớp (tĩnh). Kiểm tra các liên kết đơn bị khóa đôi trong google và bạn có thể thấy rằng sự hiểu biết của bạn là sai. stackoverflow.com/questions/18093735/
Mạnh

biến động tĩnh riêng là khai báo hợp lệ.
Ravindra babu

0

truy cập giá trị biến dễ bay hơi sẽ được trực tiếp từ bộ nhớ chính. Nó chỉ nên được sử dụng trong môi trường đa luồng. biến tĩnh sẽ được tải một lần. Nếu nó được sử dụng trong môi trường luồng đơn, ngay cả khi bản sao của biến sẽ được cập nhật và sẽ không có hại khi truy cập vào nó vì chỉ có một luồng.

Bây giờ nếu biến tĩnh được sử dụng trong môi trường đa luồng thì sẽ có vấn đề nếu người ta mong đợi kết quả mong muốn từ nó. Vì mỗi luồng có bản sao riêng của chúng nên bất kỳ sự tăng hoặc giảm nào trên biến tĩnh từ một luồng có thể không phản ánh trong luồng khác.

Nếu người ta mong đợi kết quả mong muốn từ biến tĩnh thì sử dụng biến động với tĩnh trong đa luồng thì mọi thứ sẽ được giải quyết.


0

Không chắc chắn các biến tĩnh được lưu trong bộ nhớ cục bộ của luồng hoặc KHÔNG. Nhưng khi tôi thực hiện hai luồng (T1, T2) truy cập vào cùng một đối tượng (obj) và khi cập nhật được thực hiện bởi luồng T1 thành biến tĩnh, nó đã được phản ánh trong T2.


-2

Nếu chúng ta khai báo một biến là tĩnh, sẽ chỉ có một bản sao của biến đó. Vì vậy, bất cứ khi nào các luồng khác nhau truy cập vào biến đó, sẽ chỉ có một giá trị cuối cùng cho biến đó (vì chỉ có một vị trí bộ nhớ được phân bổ cho biến đó).

Nếu một biến được khai báo là không ổn định, tất cả các luồng sẽ có bản sao biến riêng nhưng giá trị được lấy từ bộ nhớ chính. Vì vậy, giá trị của biến trong tất cả các luồng sẽ giống nhau.

Vì vậy, trong cả hai trường hợp, điểm chính là giá trị của biến là giống nhau trên tất cả các luồng.


15
Nếu một biến được khai báo là không ổn định, tất cả các luồng sẽ có bản sao của biến đó nhưng giá trị được lấy từ bộ nhớ chính. => đúng. Vì vậy, giá trị của biến trong tất cả các luồng sẽ giống nhau. => sai, mỗi luồng sẽ sử dụng cùng một giá trị cho cùng một Đối tượng, nhưng mỗi Đối tượng sẽ có bản sao riêng.
stivlo
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.