Khi phát triển ứng dụng Android, các nhà phát triển thường phải đối mặt với sự phức tạp của Concurrency trong xử lý các tác vụ nền, request mạng và cập nhật giao diện người dùng trong môi trường đa luồng. Dưới đây là một phân tích chi tiết về các phương pháp chính để quản lý concurrency một cách hiệu quả.
Synchronized
Khái niệm
Từ khoá Synchronized sẽ đảm bảo tại một thời điểm, chỉ được phép có 1 Thread được truy nhập vào một đoạn mã găng (Critical Sections). Mã găng là các đoạn mã mà khi khởi chạy chúng trên nhiều thread sẽ dẫn tới việc đọc ghi chung biến, file ... (Tổng quát hơn là dữ liệu). Ví dụ:
class FileManager { @Volatile private var isWriting = false @Synchronized fun writeToFile(data: String) { isWriting = true // Write data to file isWriting = false }
}
Trong đoạn code trên, tại một thời điểm sẽ chỉ có 1 Thread được sử dung hàm writeToFile để ghi file.
Các trường hợp sử dụng
- Bảo Vệ Tài Nguyên Chia Sẻ: Khi nhiều luồng cần truy cập và sửa đổi một tài nguyên chia sẻ như thao tác I/O với file, thao tác mạng trong các lớp tùy chỉnh hoặc các đối tượng truy cập dữ liệu (DAOs). Synchronized ngăn chặn sự hỏng hóc dữ liệu và đảm bảo an toàn luồng.
- Cập Nhật Giao Diện Người Dùng An Toàn Luồng: Sử dụng để cập nhật giao diện người dùng từ các luồng nền trong khi duy trì an toàn luồng. Việc này giúp tránh các ngoại lệ hoặc hành vi không mong muốn trong giao diện người dùng.
- Các Thành Phần Có Thể Tái Sử Dụng: Các developer có kinh nghiệm có thể tận dụng các khối synchronized để tạo ra các thành phần có thể tái sử dụng và an toàn luồng có thể được sử dụng ở các phần khác nhau của ứng dụng.
Volatile
Khái niệm
Một biến volatile đảm bảo rằng các thay đổi được thực hiện bởi một Thread sẽ ngay lập tức hiển thị cho tất cả các Thread khác. Việc này sẽ đảm bảo dữ liệu được lưu trữ bởi biến sẽ luôn đồng bộ với tất cả các luồng, nhưng không đảm bảo độc quyền truy cập.
@Volatile
private var downloadCompleted = false fun downloadData() { // Download data in background thread downloadCompleted = true
} fun checkDownloadStatus(): Boolean { return downloadCompleted
}
Trong đoạn code trên, biến cờ downloadCompleted là Volatile và sẽ luôn đồng bộ với tất cả các luồng. Khi các Background thread đang thực hiện download và cập nhật cờ này, ta có thể đứng từ UI thread để kiểm tra trạng thái cờ và chắc chắn rằng nó đúng.
Các trường hợp sử dụng
- Giao Tiếp Luồng Nhẹ: Để thông báo sự kiện hoặc thay đổi trạng thái ứng dụng giữa các luồng. Ví dụ, thông báo cho một Background Thread rằng một yêu cầu mạng đã hoàn thành.
- Kiểm Tra Trạng Thái Nhanh: Khi nhiều luồng cần kiểm tra nhanh các cờ hoặc biến trạng thái, chẳng hạn như một boolean chỉ ra xem một hoạt động có bị tạm dừng hay không.
Atomic
Khái niệm
Các Atomic operation là đơn vị thực thi nhỏ nhất, không thể bị phân chia (mức nguyên tử). Atomic operation đảm bảo rằng một Operation ở mức nguyên tử chỉ có thể có hai trạng thái: hoặc thành công (success), hoặc thất bại (fail) không thể bị gián đoạn bởi bất kỳ thao tác nào khác.
val counter = AtomicInteger(0) fun incrementCounterAtomically() { counter.incrementAndGet()
}
Nhìn vào đoạn code trên, nhiệm vụ của nó là tăng giá trị của một biến counter. Nếu có nhiều Thread được quyền truy cập vào biến này, giá trị của counter sẽ có thể phụ thuộc vào thứ tự các Thread được thực thi hoặc có nhiều luồng cùng tác động (race codition), điều này là không nên. Còn khi sử dụng Atomic, việc thao tác với counter sẽ được đảm bảo đồng bộ cho đến khi hoàn thành, không Thread nào có thể xen vào.
Các trường hợp sử dụng
- Cập nhật dữ liệu một cách chính xác, các Thread khác không thể xen vào, phòng tránh race codition
- Tối ưu hoá hiệu suất
- Lập trình bất đồng bộ: kết hợp Atomic và Coroutine trong lập trình bất đồng bộ để cập nhật các dữ liệu một cách an toàn. Kết luận Lập trình concurrency là kỹ năng khó nhưng quan trọng. Cần phải chọn phương pháp lập trình đồng thời phù hợp với từng ứng dụng, từng thuật toán để đảm bảo hiệu suất và an toàn luồng.