Bạn đang hỏi cụ thể về cách họ làm việc nội bộ , vì vậy bạn ở đây:
Không đồng bộ hóa
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Về cơ bản, nó đọc giá trị từ bộ nhớ, tăng nó và đưa trở lại bộ nhớ. Điều này hoạt động trong một luồng đơn nhưng ngày nay, trong thời đại của bộ nhớ cache đa lõi, đa CPU, đa cấp, nó sẽ không hoạt động chính xác. Trước hết, nó giới thiệu điều kiện cuộc đua (một số chủ đề có thể đọc giá trị cùng một lúc), nhưng cũng có vấn đề về khả năng hiển thị. Giá trị chỉ có thể được lưu trữ trong bộ nhớ CPU " cục bộ " (một số bộ đệm) và không hiển thị cho các CPU / lõi khác (và do đó - các luồng). Đây là lý do tại sao nhiều người đề cập đến bản sao cục bộ của một biến trong một luồng. Nó rất không an toàn. Xem xét mã dừng phổ biến nhưng bị hỏng này:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Thêm volatile
vào stopped
biến và nó hoạt động tốt - nếu bất kỳ luồng nào khác sửa đổi stopped
biến qua pleaseStop()
phương thức, bạn được đảm bảo sẽ thấy thay đổi đó ngay lập tức trong while(!stopped)
vòng lặp của luồng làm việc . BTW đây cũng không phải là một cách hay để làm gián đoạn một luồng, xem: Cách dừng một luồng đang chạy mãi mà không sử dụng và Dừng một luồng java cụ thể .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Các AtomicInteger
sử dụng lớp CAS ( so sánh-và-swap ) hoạt động CPU ở mức độ thấp (không đồng bộ cần thiết!) Chúng cho phép bạn sửa đổi một biến đặc biệt chỉ khi giá trị hiện tại bằng cái gì khác (và được trả về thành công). Vì vậy, khi bạn thực thi, getAndIncrement()
nó thực sự chạy trong một vòng lặp (thực hiện đơn giản hóa thực tế):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Về cơ bản: đọc; cố gắng lưu trữ giá trị gia tăng; nếu không thành công (giá trị không còn bằng current
), hãy đọc và thử lại. Điều compareAndSet()
này được thực hiện trong mã gốc (lắp ráp).
volatile
không đồng bộ
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Mã này không đúng. Nó khắc phục vấn đề về khả năng hiển thị ( volatile
đảm bảo các luồng khác có thể thấy thay đổi được thực hiện counter
) nhưng vẫn có điều kiện cuộc đua. Điều này đã được giải thích nhiều lần: trước / sau tăng không phải là nguyên tử.
Tác dụng phụ duy nhất của việc volatile
" xóa " bộ đệm để tất cả các bên khác thấy phiên bản mới nhất của dữ liệu. Điều này là quá nghiêm ngặt trong hầu hết các tình huống; đó là lý do tại sao volatile
không được mặc định
volatile
không đồng bộ hóa (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
Vấn đề tương tự như trên, nhưng thậm chí còn tồi tệ hơn vì i
không phải private
. Điều kiện cuộc đua vẫn còn. Tại sao nó là một vấn đề? Nếu, giả sử, hai luồng chạy mã này đồng thời, đầu ra có thể + 5
hoặc + 10
. Tuy nhiên, bạn được đảm bảo để xem sự thay đổi.
Nhiều độc lập synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Thật ngạc nhiên, mã này là không chính xác là tốt. Trên thực tế, nó hoàn toàn sai. Trước hết, bạn đang đồng bộ hóa i
, sắp được thay đổi (hơn nữa, i
là một nguyên thủy, vì vậy tôi đoán bạn đang đồng bộ hóa trên một tạm thời Integer
được tạo thông qua hộp tự động ...) Hoàn toàn sai sót. Bạn cũng có thể viết:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
Không có hai luồng có thể vào cùng một synchronized
khối với cùng một khóa . Trong trường hợp này (và tương tự trong mã của bạn), đối tượng khóa thay đổi theo mỗi lần thực thi, do đó, synchronized
không có hiệu lực.
Ngay cả khi bạn đã sử dụng một biến cuối cùng (hoặc this
) để đồng bộ hóa, mã vẫn không chính xác. Hai luồng đầu tiên có thể đọc i
thành temp
đồng bộ (có cùng giá trị cục bộ temp
), sau đó đầu tiên gán giá trị mới cho i
(giả sử, từ 1 đến 6) và một luồng khác thực hiện cùng một điều (từ 1 đến 6).
Việc đồng bộ hóa phải trải dài từ đọc đến gán giá trị. Đồng bộ hóa đầu tiên của bạn không có hiệu lực (đọc một int
là nguyên tử) và thứ hai là tốt. Theo tôi, đây là những hình thức chính xác:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}