Khái niệm

Trong lĩnh vực lập trình Race condition là một tình huống xảy ra khi nhiều threads cùng truy cập và cùng lúc muốn thay đổi dữ liệu (có thể là 1 biến, 1 row trong database, 1 vùng shared data, memory , etc...). Vì thuật toán chuyển đổi việc thực thi giữa các threads có thể xảy ra bất cứ lúc nào nên không thể biết được thứ tự của các threads truy cập và thay đổi dữ liệu đó sẽ dẫn đến giá trị của data sẽ không như mong muốn. Kết quả sẽ phụ thuộc vào thuật toán thread scheduling của hệ điều hành.

Quá trình các thread thực thi lệnh trông như 1 cuộc đua giữa các vận động viên điền kinh olympic vì vậy có thể liên tưởng đến thuật ngữ (keyword) “Race condition”.

Ví dụ cụ thể
Một ví dụ đơn giản để trình bày “Race condition”.

  • Với một function:
public class Counter {
    protected long count = 0;
    public void add(long value) {
         this.count = this.count + value;
    }
}

Tình huống có 2 thread A và B cùng thực thi phương thức add trên 1 thể hiện của class Counter các bước sẽ như sau:

  1. Đọc giá trị count từ memory.
  2. Cộng value vào count.
  3. Gán lại giá trị count.

Quá trình thực thi của thread A và B có thể diễn ra như sau:

this.count = 0
A: Đọc giá trị this.count 
B: Đọc giá trị this.count
B: Cộng giá trị a vào this.count 
B: Cập nhật lại giá trị this.count
A: Cộng giá trị b vào this.count
A: Cập nhật lại giá trị this.count

Example

Vậy sau khi thực thi giá trị của this.count là this.count + a hoặc this.count + b, hoàn toàn không kiểm soát được!
Để ngăn chặn việc đó xảy ra phải để hàm add() được thực thi tuần tự. Có thể lock hoặc synchronized method add().

public class Counter {
    protected long count = 0;
    public synchronized void add(long value) {
         this.count = this.count + value;
    }
}

Kinh nghiệm trong java

Như đã thấy race condition là vấn đề thường xuyên xảy ra trong lập trình. Với kinh nghiệm về java khi sử dụng spring boot, spring jpa và mysql đã gặp phải tình huống trên. Các giải pháp tôi biết như sau:

  1. Dùng một message queue để chứa và xử lí tuần tự việc đọc, ghi dữ liệu.
  2. Sử dụng cơ chế Locking trong JPA để đồng bộ dữ liệu.
  3. Sử dụng câu update trực tiếp và đưa phần tính toán xuống data base. Ví dụ với bài toán chuyển tiền của các ngân hang tôi có thể làm như sau
update table t set t.balance = t.balance + amount;

Tôi đã dùng phương án 3, vì theo tôi:

• Phương án 1 tất cả tài khoản của user đều phải xử lý tuần tự rất tốn thời gian để làm và không có được hiệu suất tốt nhất.

• Phương án 2 thì chúng ta phải try catch exception và xử lí lại record đó nên theo tôi cũng không phải giải pháp tốt.

Còn bạn thì sao? Bạn sẽ chọn giải pháp nào? Hoặc bạn sẽ có giải pháp nào cho hệ thống của bạn? Hãy chia sẽ để mọi người cùng tham khảo nhá.