Hôm nay chúng ta sẽ cùng tìm hiểu về AtomicInteger trong Java.
Các thao tác nguyên tử (atomic operations) được thực hiện trọn vẹn trong một đơn vị xử lý duy nhất, không bị gián đoạn hay can thiệp bởi bất kỳ thao tác nào khác.
Trong môi trường đa luồng, các thao tác nguyên tử là rất cần thiết để tránh xảy ra tình trạng dữ liệu không nhất quán*.*
AtomicInteger trong Java
Hãy cùng tạo một chương trình đa luồng đơn giản, trong đó mỗi luồng sẽ tăng một biến đếm dùng chung (count
) lên 4 lần.
Vì vậy, nếu có hai luồng, sau khi cả hai hoàn tất, giá trị (count
) nên là 8.
package com.journaldev.concurrency;
public class JavaAtomic {
public static void main(String[] args) throws InterruptedException {
ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "t1");
t1.start();
Thread t2 = new Thread(pt, "t2");
t2.start();
t1.join();
t2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ProcessingThread implements Runnable {
private int count;
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count++;
}
}
public int getCount() {
return this.count;
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Nếu bạn chạy chương trình ở trên, bạn sẽ nhận thấy rằng giá trị của count
có thể dao động trong khoảng 5, 6, 7 hoặc 8. Lý do là bởi vì phép toán count++
không phải là một thao tác nguyên tử. Khi một luồng đọc giá trị hiện tại và chuẩn bị tăng lên 1, một luồng khác có thể đã đọc cùng một giá trị cũ, dẫn đến kết quả cuối cùng bị sai.
Cách giải quyết: Để khắc phục vấn đề này, chúng ta cần đảm bảo rằng phép tăng count
là một thao tác nguyên tử.
Có thể làm điều này bằng cách dùng từ khóa synchronized
, nhưng bắt đầu từ Java 5, gói java.util.concurrent.atomic
đã cung cấp các lớp bao (wrapper classes) như AtomicInteger
và AtomicLong
, giúp thực hiện các thao tác nguyên tử mà không cần dùng synchronized
.
Ví dụ về AtomicInteger trong Java
Dưới đây là phiên bản đã cập nhật của chương trình, sử dụng AtomicInteger
phương thức incrementAndGet()
, chương trình sẽ luôn xuất ra giá trị count
cuối cùng là 8, vì mỗi lần tăng đều được xử lý nguyên tử, không có xung đột giữa các luồng.
package com.journaldev.concurrency;
import java.util.concurrent.atomic.AtomicInteger;
public class JavaAtomic {
public static void main(String[] args) throws InterruptedException {
ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "t1");
t1.start();
Thread t2 = new Thread(pt, "t2");
t2.start();
t1.join();
t2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ProcessingThread implements Runnable {
private AtomicInteger count = new AtomicInteger();
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count.incrementAndGet();
}
}
public int getCount() {
return this.count.get();
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Lợi ích của việc sử dụng các lớp đồng thời (concurrency classes) để thực hiện các thao tác nguyên tử là chúng ta không cần lo lắng về việc đồng bộ hóa (synchronization). Điều này giúp mã nguồn dễ đọc hơn và giảm khả năng xảy ra lỗi trong quá trình xử lý.
Bên cạnh đó, các lớp đồng thời hỗ trợ thao tác nguyên tử thường được đánh giá là hiệu quả hơn so với cơ chế đồng bộ hóa truyền thống, vốn đòi hỏi phải khóa và quản lý tài nguyên trong lúc thực thi.