Lập Lịch Luồng
(Thread Scheduler)
Lập lịch luồng (Thread Scheduler) trong Java là một thành phần quan trọng, quyết định luồng nào sẽ được chạy hoặc thực thi và luồng nào sẽ phải chờ. Trong Java, một luồng chỉ được Lập Lịch Luồng (Thread Scheduler) chọn nếu nó đang ở trạng thái sẵn sàng (runnable). Tuy nhiên, nếu có nhiều luồng ở trạng thái sẵn sàng, thì việc chọn một trong các luồng này để chạy là nhiệm vụ của Lập Lịch Luồng (Thread Scheduler), và các luồng khác sẽ bị bỏ qua. Có một số tiêu chí để quyết định luồng nào sẽ được thực thi trước. Có hai yếu tố chính trong việc lập lịch cho một luồng là Độ ưu tiên (Priority) và Thời gian đến (Time of Arrival).
Độ ưu tiên (Priority): Mỗi luồng có độ ưu tiên nằm trong khoảng từ 1 đến 10. Nếu một luồng có độ ưu tiên cao hơn, điều đó có nghĩa là luồng đó có cơ hội được Thread Scheduler chọn để thực thi tốt hơn.
Thời gian đến (Time of Arrival): Giả sử có hai luồng có cùng độ ưu tiên và cả hai đều vào trạng thái sẵn sàng, khi đó độ ưu tiên sẽ không thể là yếu tố quyết định để chọn một trong hai luồng này. Trong trường hợp đó, Thread Scheduler sẽ xem xét thời gian đến của luồng. Luồng nào đến trước sẽ được ưu tiên thực thi trước các luồng khác.
Dựa trên các yếu tố đã nêu, thuật toán lập lịch sẽ được Bộ lập lịch luồng trong Java thực hiện.
Lập lịch theo kiểu First Come First Serve (FCFS)
↳ Nguyên lý: Luồng (thread) hoặc tiến trình (process) nào đến trước thì được phục vụ trước. Đây là thuật toán lập lịch đơn giản nhất.
↳ Ưu điểm: Đơn giản, dễ hiểu và triển khai.
↳ Nhược điểm:
↳ Không ưu tiên các công việc quan trọng: Luồng ngắn có thể phải chờ lâu nếu có luồng dài đến trước.
↳ Có thể dẫn đến tình trạng đói: Nếu liên tục có các luồng mới đến, một luồng nào đó có thể không bao giờ được thực thi.

Lập lịch chia thời gian (Time-slicing scheduling)
↳ Nguyên lý: Mỗi tiến trình được cấp phát một khoảng thời gian nhất định (time slice hoặc quantum) để chạy. Khi thời gian đó hết, nếu tiến trình chưa hoàn thành, nó sẽ bị dừng lại và đưa vào hàng đợi, nhường quyền cho tiến trình khác.
↳ Ưu điểm:
↳ Cân bằng hơn giữa các tiến trình, không có tiến trình nào chiếm toàn bộ CPU quá lâu.
↳ Cải thiện thời gian phản hồi (response time) cho các tiến trình tương tác.
↳ Nhược điểm:
↳ Cần chọn giá trị time slice thích hợp, nếu quá ngắn sẽ làm tăng overhead, nếu quá dài sẽ giống với FCFS.
↳ Có thể gây ra lãng phí thời gian nếu tiến trình có thời gian xử lý nhỏ hơn time slice.

Lên kế hoạch ưu tiên chiếm đoạt (Preemptive-Priority Scheduling)
↳ Nguyên lý: Mỗi tiến trình được gán một mức ưu tiên. CPU sẽ luôn chọn tiến trình có mức ưu tiên cao nhất để chạy. Nếu một tiến trình mới đến có ưu tiên cao hơn tiến trình hiện tại, tiến trình hiện tại sẽ bị dừng lại (bị "chiếm đoạt") và tiến trình mới sẽ được chạy.
↳ Ưu điểm:
↳ Ưu tiên các công việc quan trọng: Các luồng có ưu tiên cao sẽ được thực thi trước.
↳ Phù hợp với các hệ thống thực thời: Đảm bảo các tác vụ quan trọng được hoàn thành đúng hạn.
↳ Nhược điểm:
↳ Có thể dẫn đến tình trạng đói: Nếu liên tục có các luồng mới có ưu tiên cao hơn, một luồng có ưu tiên thấp có thể không bao giờ được thực thi.
↳ Việc xác định ưu tiên có thể phức tạp: Không phải lúc nào cũng dễ dàng xác định ưu tiên của các luồng.
↳ Để hiểu cách thức hoạt động của Java Thread Scheduler, chúng ta cần biết rằng Thread Scheduler là một phần của hệ điều hành hoặc JVM (Java Virtual Machine) có nhiệm vụ quản lý các luồng (threads) để quyết định luồng nào sẽ được cấp CPU để thực thi.

Cách thức hoạt động của lập lịch luồng (Thread Scheduler)
Ưu tiên luồng (Thread Priority):
↳ Mỗi luồng trong Java có một mức độ ưu tiên, được thiết lập bằng phương thức setPriority(int priority).
↳ Mức độ ưu tiên của luồng giúp Thread Scheduler quyết định luồng nào sẽ được cấp CPU trước. Luồng có mức độ ưu tiên cao hơn sẽ được chọn trước.
Chuyển đổi giữa các luồng:
↳ Nếu một luồng đang ở trạng thái Runnable (sẵn sàng chạy) và có một luồng khác với mức độ ưu tiên cao hơn cũng ở trạng thái Runnable, Thread Scheduler sẽ tạm dừng luồng hiện tại và chuyển quyền điều khiển cho luồng có mức độ ưu tiên cao hơn. Đây là cơ chế Preemption (chiếm đoạt) trong lập lịch.
Cùng mức độ ưu tiên và cùng thời gian đến:
↳ Nếu có hai luồng (Thread 2 và Thread 3) có cùng mức độ ưu tiên và cùng thời gian đến, Thread Scheduler sẽ quyết định luồng nào sẽ chạy trước dựa trên thuật toán First Come First Serve (FCFS). Tức là, luồng nào đến trước sẽ được chạy trước.
Ví dụ cụ thể:
Giả sử có 5 luồng: Thread 1, Thread 2, Thread 3, Thread 4, Thread 5 với các mức độ ưu tiên và thời gian đến khác nhau.
↳ Thread 1: Đến lúc 0ms, ưu tiên 5
↳ Thread 2: Đến lúc 2ms, ưu tiên 7
↳ Thread 3: Đến lúc 2ms, ưu tiên 7
↳ Thread 4: Đến lúc 4ms, ưu tiên 3
↳ Thread 5: Đến lúc 5ms, ưu tiên 9
Thread Scheduler sẽ hoạt động như sau:
↳ Lúc 0ms, Thread 1 được chọn vì là luồng duy nhất, nó bắt đầu thực thi.
↳ Lúc 2ms, Thread 2 và Thread 3 đến. Thread 1 bị tạm dừng vì Thread 2 và Thread 3 có mức độ ưu tiên cao hơn (7 so với 5). Tuy nhiên, vì Thread 2 đến trước, nó sẽ được chạy trước.
↳ Lúc 4ms, Thread 4 đến nhưng vì có mức độ ưu tiên thấp (3), nên Thread Scheduler sẽ bỏ qua.
↳ Lúc 5ms, Thread 5 đến với mức độ ưu tiên cao nhất (9). Thread Scheduler tạm dừng Thread 2 và chuyển sang thực thi Thread 5.
Kết quả là:
↳ Thread 5 được thực thi trước tiên vì có mức độ ưu tiên cao nhất.
↳ Sau đó là Thread 2, rồi đến Thread 3 (vì cả hai có cùng ưu tiên nhưng Thread 2 đến trước).
↳ Thread 1 và Thread 4 sẽ được thực thi sau cùng dựa trên thứ tự mức độ ưu tiên và thời gian đến.
↳ Điều này cho thấy rằng Thread Scheduler sẽ luôn ưu tiên những luồng có mức độ ưu tiên cao hơn, và khi gặp các luồng có cùng ưu tiên, nó sẽ sử dụng thuật toán FCFS để quyết định.