Java: Giao thoa luồng


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Giao tiếp luồng chủ yếu là được thực hiện bằng cách chia sẻ quyền truy cập tới các trường và các đối tượng sẽ tham chiếu tới các trường này. Đây là hình thức giao tiếp cực kỳ hiệu quả, nhưng có thể gây ra hai loại lỗi: lỗi can thiệp luồng và lỗi nhất quán bộ nhớ. Công cụ cần thiết để ngăn chặn các lỗi này là đồng bộ hóa.

Tuy nhiên, đồng bộ hóa có thể gây ra xung đột luồng, điều này xảy ra khi hai hay nhiều luồng cố gắng truy cập vào tài nguyên cùng một lúc và gây ra lỗi runtime dẫn đến việc thực thi một hoặc nhiều luồng trở nên chậm hơn, hoặc thậm chí là bị treo. Starvation và livelock là các biểu hiện của xung đột luồng.

Ta hãy xét một lớp đơn giản có tên là Counter như sau:

class Counter {

    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

Counter được thiết kế sao cho mỗi lời gọi đến phương thức increment() sẽ thêm 1 cho biến c, và mỗi lời gọi đến phương thức decrement() sẽ giảm 1 cho biến c. Tuy nhiên, nếu một đối tượng Counter được tham chiếu từ nhiều luồng, thì sự giao thoa giữa các luồng có thể không thực hiện được những phương thức này như mong muốn.

Sự giao thoa xảy ra khi hai hoạt động đang chạy trong các luồng khác nhau, nhưng lại thực thi trên cùng một dữ liệu dẫn đến hiện tượng đan xen. Điều này có nghĩa rằng hai hoạt động sẽ bao gồm nhiều bước, và trình tự các bước sẽ chồng lên nhau.

Hiện tượng đan xen này có vẻ là không tốt cho các hoạt động trên các thể hiện của lớp Counter, vì cả hai hoạt động trên biến c đều là duy nhất, mặc dù các câu lệnh đều rất đơn giản. Tuy nhiên, máy ảo sẽ giúp các câu lệnh đơn giản này có thể được thực hiện trong nhiều bước. Chúng tôi sẽ không xem xét các bước cụ thể mà máy ảo thực hiện, nhưng chúng ta cũng đủ hiểu rằng câu lệnh c++; có thể được phân thành ba bước:

  1. Lấy giá trị hiện tại của biến c.
  2. Tăng giá trị lên 1.
  3. Lưu trữ giá trị sau khi tăng ngược trở lại cho c.

Câu lệnh c--; cũng có các bước tương tự, nhưng là giảm giá trị của c đi 1.

Giả sử luồng A gọi phương thức increment() đúng vào lúc luồng B gọi decrement(). Nếu giả sử ban đầu c=0 thì các hành động xen kẽ có thể là như sau:

  1. Luồng A: Lấy c.
  2. Luồng B: Lấy c.
  3. Luồng A: Tăng giá trị cho c; Kết quả là 1.
  4. Luồng B: Giảm giá trị cho c; Kết quả là -1.
  5. Luồng A: Lưu trữ kết quả trong c; Lúc này c=1.
  6. Luồng B: Lưu trữ kết quả trong c; Lúc này c=-1.

Như vậy thì kết quả của luồng A sẽ bị mất bởi luồng B đã ghi đè. Đặc tính xen kẽ này chỉ là một ví dụ. Trong những trường hợp khác có thể là kết quả của luồng B bị mất, hoặc có thể là không xảy ra lỗi. Vì không thể đoán trước, nên lỗi giao thoa luồng có thể khó phát hiện và sửa.

» Tiếp: Lỗi nhất quán bộ nhớ
« Trước: Ví dụ SimpleThreads
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!