CodeGym /Các khóa học /All lectures for VI purposes /Hoạt động nguyên tử trong Java

Hoạt động nguyên tử trong Java

All lectures for VI purposes
Mức độ , Bài học
Có sẵn

Điều kiện tiên quyết cho sự xuất hiện của các hoạt động nguyên tử

Hãy xem ví dụ này để giúp bạn hiểu cách thức hoạt động của các hoạt động nguyên tử:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

Khi chúng tôi có một luồng, mọi thứ hoạt động tốt, nhưng nếu chúng tôi thêm đa luồng, chúng tôi sẽ nhận được kết quả sai và tất cả là do thao tác gia tăng không phải là một thao tác mà là ba thao tác: yêu cầu lấy giá trị hiện tạiđếm, sau đó tăng nó lên 1 và ghi lại vàođếm.

Và khi hai luồng muốn tăng một biến, rất có thể bạn sẽ bị mất dữ liệu. Nghĩa là, cả hai luồng đều nhận được 100, do đó, cả hai sẽ ghi 101 thay vì giá trị mong đợi là 102.

Và làm thế nào để giải quyết nó? Bạn cần sử dụng ổ khóa. Từ khóa được đồng bộ hóa giúp giải quyết vấn đề này, sử dụng nó mang lại cho bạn sự đảm bảo rằng một chuỗi sẽ truy cập phương thức tại một thời điểm.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

Ngoài ra, bạn cần thêm từ khóa dễ bay hơi để đảm bảo khả năng hiển thị chính xác của các tham chiếu giữa các chủ đề. Chúng tôi đã xem xét công việc của mình ở trên.

Nhưng vẫn có những nhược điểm. Vấn đề lớn nhất là hiệu suất, tại thời điểm đó khi nhiều luồng đang cố lấy khóa và một luồng có cơ hội ghi, phần còn lại của luồng sẽ bị chặn hoặc tạm dừng cho đến khi luồng được giải phóng.

Tất cả các quá trình này, chặn, chuyển sang trạng thái khác đều rất tốn kém cho hiệu suất hệ thống.

hoạt động nguyên tử

Thuật toán sử dụng các lệnh máy cấp thấp như so sánh và hoán đổi (CAS, so sánh và hoán đổi, đảm bảo tính toàn vẹn của dữ liệu và đã có một lượng lớn nghiên cứu về chúng).

Một hoạt động CAS điển hình hoạt động trên ba toán hạng:

  • Dung lượng bộ nhớ cho công việc (M)
  • Giá trị kỳ vọng hiện có (A) của một biến
  • Giá trị mới (B) được đặt

CAS cập nhật nguyên tử M thành B, nhưng chỉ khi giá trị của M giống với A, nếu không thì không có hành động nào được thực hiện.

Trong trường hợp thứ nhất và thứ hai, giá trị của M sẽ được trả về. Điều này cho phép bạn kết hợp ba bước, đó là lấy giá trị, so sánh giá trị và cập nhật giá trị. Và tất cả biến thành một thao tác ở cấp độ máy.

Thời điểm một ứng dụng đa luồng truy cập vào một biến và cố cập nhật nó và CAS được áp dụng, thì một trong các luồng sẽ nhận được biến đó và có thể cập nhật nó. Nhưng không giống như khóa, các luồng khác sẽ chỉ gặp lỗi về việc không thể cập nhật giá trị. Sau đó, họ sẽ chuyển sang công việc tiếp theo và việc chuyển đổi hoàn toàn bị loại trừ trong loại công việc này.

Trong trường hợp này, logic trở nên khó khăn hơn do thực tế là chúng ta phải xử lý tình huống khi thao tác CAS không hoạt động thành công. Chúng ta sẽ chỉ lập mô hình mã để nó không tiếp tục cho đến khi hoạt động thành công.

Giới thiệu về các loại nguyên tử

Bạn đã từng gặp trường hợp cần thiết lập đồng bộ hóa cho biến đơn giản nhất có kiểu int chưa?

Cách đầu tiên chúng tôi đã đề cập là sử dụng dễ bay hơi + được đồng bộ hóa . Nhưng cũng có các lớp Atomic* đặc biệt.

Nếu chúng ta sử dụng CAS, thì các thao tác sẽ hoạt động nhanh hơn so với phương pháp đầu tiên. Và ngoài ra, chúng tôi có các phương pháp đặc biệt và rất thuận tiện để thêm một giá trị cũng như các phép toán tăng và giảm.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray là các lớp trong đó các hoạt động là nguyên tử. Dưới đây chúng tôi sẽ phân tích công việc với họ.

nguyên tử số nguyên

Lớp AtomicInteger cung cấp các phép toán trên một giá trị int có thể được đọc và ghi nguyên tử, ngoài việc cung cấp các phép toán nguyên tử mở rộng.

Nó có các phương thức getset hoạt động như đọc và ghi các biến.

Đó là, "xảy ra trước" với bất kỳ lần nhận nào tiếp theo của cùng một biến mà chúng ta đã nói trước đó. Phương thức so sánh nguyên tử cũng có các tính năng nhất quán bộ nhớ này.

Tất cả các hoạt động trả về một giá trị mới được thực hiện nguyên tử:

int addAndGet (int đồng bằng) Thêm một giá trị cụ thể vào giá trị hiện tại.
so sánh booleanAndSet(int dự kiến, cập nhật int) Đặt giá trị thành giá trị được cập nhật nhất định nếu giá trị hiện tại khớp với giá trị dự kiến.
int giảmAndGet() Giảm giá trị hiện tại xuống một.
int getAndAdd(int delta) Thêm giá trị đã cho vào giá trị hiện tại.
int getAndDecrement() Giảm giá trị hiện tại xuống một.
int getAndIncrement() Tăng giá trị hiện tại lên một.
int getAndSet(int newValue) Đặt giá trị đã cho và trả về giá trị cũ.
int gia tăngAndGet() Tăng giá trị hiện tại lên một.
lazySet(int newValue) Cuối cùng đặt thành giá trị đã cho.
boolean yếuCompareAndSet(dự kiến, cập nhật int) Đặt giá trị thành giá trị được cập nhật nhất định nếu giá trị hiện tại khớp với giá trị dự kiến.

Ví dụ:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION