"Chào, Amigo!"
"Tôi muốn tìm hiểu sâu hơn với bạn về thông báo chờ. Các phương thức thông báo chờ cung cấp một cơ chế thuận tiện cho các luồng tương tác. Chúng cũng có thể được sử dụng để xây dựng các cơ chế cấp cao phức tạp cho tương tác luồng."
"Tôi sẽ bắt đầu với một ví dụ nhỏ. Giả sử chúng ta có một chương trình dành cho máy chủ phải thực hiện các tác vụ khác nhau do người dùng tạo thông qua một trang web. Người dùng có thể thêm các tác vụ khác nhau vào các thời điểm khác nhau. Các tác vụ này sử dụng nhiều tài nguyên, nhưng máy chủ của chúng tôi 8 bộ xử lý -core có thể xử lý được. Chúng tôi nên thực hiện các tác vụ trên máy chủ như thế nào?"
"Đầu tiên, chúng tôi sẽ tạo một nhóm các luồng công nhân, số lượng tùy theo số lượng lõi bộ xử lý. Mỗi luồng sẽ có thể chạy trên lõi của chính nó: Các luồng sẽ không can thiệp lẫn nhau và các lõi bộ xử lý sẽ không Ngồi nhàn rỗi."
"Thứ hai, chúng tôi sẽ tạo một đối tượng hàng đợi nơi các tác vụ của người dùng sẽ được thêm vào. Các loại tác vụ khác nhau sẽ tương ứng với các đối tượng khác nhau, nhưng tất cả chúng sẽ triển khai giao diện Runnable để chúng có thể chạy được."
"Bạn có thể cho tôi một ví dụ về đối tượng nhiệm vụ không?"
"Kiểm tra xem:"
class Factorial implements Runnable
{
public int n = 0;
public long result = 1;
public Factorial (int n)
{
this.n = n;
}
public void run()
{
for (int i = 2; i <= n; i++)
result *= i;
}
}
"Càng xa càng tốt."
"Tuyệt. Sau đó, hãy kiểm tra xem một đối tượng hàng đợi sẽ trông như thế nào. Bạn có thể cho tôi biết gì về nó?"
"Nó phải an toàn cho luồng. Nó được tải với các đối tượng tác vụ bởi một luồng nhận chúng từ người dùng và sau đó các tác vụ được chọn bởi các luồng công nhân."
"Ừ. Và nếu chúng ta hết nhiệm vụ trong một thời gian thì sao?"
"Sau đó, chuỗi công nhân nên đợi cho đến khi có nhiều hơn."
"Đúng vậy. Bây giờ hãy tưởng tượng rằng tất cả những thứ này có thể được xây dựng trong một hàng đợi duy nhất. Hãy xem thử:"
public class JobQueue
{
ArrayList jobs = new ArrayList();
public synchronized void put(Runnable job)
{
jobs.add(job);
this.notifyAll();
}
public synchronized Runnable getJob()
{
while (jobs.size() == 0)
this.wait();
return jobs.remove(0);
}
}
"Chúng tôi có một phương thức getJob để kiểm tra xem danh sách nhiệm vụ có trống không. Sau đó, luồng sẽ chuyển sang chế độ ngủ (chờ) cho đến khi có thứ gì đó xuất hiện trong danh sách."
"Ngoài ra còn có phương thức put , cho phép bạn thêm một tác vụ mới vào danh sách. Ngay sau khi một tác vụ mới được thêm vào, phương thức notifyAll sẽ được gọi. Việc gọi phương thức này sẽ đánh thức tất cả các luồng công nhân đã ngủ quên bên trong phương thức getJob."
"Bạn có thể nhớ lại cách hoạt động của các phương thức chờ và thông báo không?"
"Phương thức chờ chỉ được gọi bên trong một khối được đồng bộ hóa, trên một đối tượng mutex. Trong trường hợp của chúng tôi: điều này. Ngoài ra, có hai điều xảy ra:
1) Chủ đề rơi vào trạng thái ngủ.
2) Chuỗi tạm thời giải phóng mutex (cho đến khi nó thức dậy).
"Sau đó, các luồng khác có thể nhập khối được đồng bộ hóa và thu được cùng một mutex đó."
" Phương thức notifyAll cũng chỉ có thể được gọi bên trong khối được đồng bộ hóa của đối tượng mutex. Trong trường hợp của chúng tôi: điều này. Ngoài ra, có hai điều xảy ra:"
1) Tất cả các luồng đang chờ trên đối tượng mutex này được đánh thức.
2) Khi luồng hiện tại thoát khỏi khối được đồng bộ hóa, một trong các luồng được đánh thức sẽ nhận được mutex và tiếp tục công việc của nó. Khi nó giải phóng mutex, một luồng khác đã được đánh thức sẽ lấy lại mutex, v.v.
"Nó rất giống với xe buýt. Bạn vào và muốn trả tiền vé, nhưng không có tài xế. Thế là bạn «ngủ gật». Cuối cùng, xe buýt chật cứng người, nhưng vẫn không có ai trả tiền. Sau đó, tài xế đến và nói, «Vé vé, làm ơn». Và đây là sự khởi đầu của…”
"Một sự so sánh thú vị. Nhưng xe buýt là gì?"
"Julio đã giải thích điều này. Có những thứ kỳ lạ được sử dụng trong thế kỷ 21."
GO TO FULL VERSION