Các vấn đề được giải quyết bằng đa luồng
Đa luồng thực sự được phát minh để đạt được hai mục tiêu quan trọng:-
Làm nhiều việc cùng một lúc.
Trong ví dụ trên, các chủ đề khác nhau (các thành viên trong gia đình) đã thực hiện song song một số hành động: họ rửa bát đĩa, đi đến cửa hàng và thu dọn đồ đạc.
Chúng tôi có thể đưa ra một ví dụ liên quan chặt chẽ hơn đến lập trình. Giả sử bạn có một chương trình với giao diện người dùng. Khi bạn nhấp vào 'Tiếp tục' trong chương trình, một số tính toán sẽ diễn ra và người dùng sẽ thấy màn hình sau. Nếu các hành động này được thực hiện tuần tự, thì chương trình sẽ chỉ bị treo sau khi người dùng nhấp vào nút 'Tiếp tục'. Người dùng sẽ thấy màn hình có màn hình nút 'Tiếp tục' cho đến khi chương trình thực hiện tất cả các tính toán bên trong và đến phần mà giao diện người dùng được làm mới.
Chà, tôi đoán chúng ta sẽ đợi vài phút!
Hoặc chúng tôi có thể làm lại chương trình của mình, hoặc, như các lập trình viên nói, 'song song hóa' nó. Hãy thực hiện các phép tính của chúng ta trên một luồng và vẽ giao diện người dùng trên một luồng khác. Hầu hết các máy tính đều có đủ tài nguyên để thực hiện việc này. Nếu chúng ta đi theo con đường này, thì chương trình sẽ không bị treo và người dùng sẽ di chuyển mượt mà giữa các màn hình mà không phải lo lắng về những gì đang xảy ra bên trong. Cái này không can thiệp vào cái kia :)
-
Thực hiện các phép tính nhanh hơn.
Mọi thứ đơn giản hơn nhiều ở đây. Nếu bộ xử lý của chúng tôi có nhiều lõi và hầu hết các bộ xử lý ngày nay đều có, thì một số lõi có thể xử lý song song danh sách các tác vụ của chúng tôi. Rõ ràng, nếu chúng ta cần thực hiện 1000 tác vụ và mỗi tác vụ mất một giây, thì một lõi có thể hoàn thành danh sách trong 1000 giây, hai lõi trong 500 giây, ba lõi trong hơn 333 giây một chút, v.v.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
Để tạo và chạy các luồng, chúng ta cần tạo một lớp, làm cho nó kế thừa java.lang . Thread và ghi đè phương thức run() của nó . Yêu cầu cuối cùng đó rất quan trọng. Chính trong phương thức run() mà chúng ta xác định logic để luồng của chúng ta thực thi. Bây giờ, nếu chúng ta tạo và chạy một thể hiện của MyFirstThread , phương thức run() sẽ hiển thị một dòng có tên: phương thức getName() hiển thị tên 'hệ thống' của luồng, được gán tự động. Nhưng tại sao chúng ta lại nói một cách ngập ngừng? Hãy tạo một cái và tìm hiểu!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Đầu ra bảng điều khiển: Tôi là Chủ đề! Tên tôi là Chủ đề-2 Tôi là Chủ đề! Tên tôi là Chủ đề-1 Tôi là Chủ đề! Tên tôi là Chủ đề-0 Tôi là Chủ đề! Tên tôi là Chủ đề-3 Tôi là Chủ đề! Tên tôi là Chủ đề-6 Tôi là Chủ đề! Tên tôi là Chủ đề-7 Tôi là Chủ đề! Tên tôi là Chủ đề-4 Tôi là Chủ đề! Tên tôi là Chủ đề-5 Tôi là Chủ đề! Tên tôi là Chủ đề-9 Tôi là Chủ đề! Tên tôi là Thread-8 Hãy tạo 10 luồng ( đối tượng MyFirstThread kế thừa Thread ) và bắt đầu chúng bằng cách gọi phương thức start() trên mỗi đối tượng. Sau khi gọi phương thức start() , logic trong phương thức run() được thực thi. Lưu ý: tên chủ đề không theo thứ tự. Thật kỳ lạ khi chúng không tuần tự:, Chủ đề-1 , Chủ đề-2 , v.v.? Khi nó xảy ra, đây là một ví dụ về thời điểm suy nghĩ 'tuần tự' không phù hợp. Vấn đề là chúng tôi chỉ cung cấp các lệnh để tạo và chạy 10 luồng. Bộ lập lịch luồng, một cơ chế đặc biệt của hệ điều hành, quyết định thứ tự thực hiện của chúng. Thiết kế chính xác và chiến lược ra quyết định của nó là những chủ đề thảo luận sâu mà chúng ta sẽ không đi sâu vào ngay bây giờ. Điều chính cần nhớ là lập trình viên không thể kiểm soát thứ tự thực hiện của các luồng. Để hiểu mức độ nghiêm trọng của tình huống, hãy thử chạy phương thức main() trong ví dụ trên một vài lần nữa. Đầu ra của bảng điều khiển trong lần chạy thứ hai: Tôi là Chủ đề! Tên tôi là Chủ đề-0 Tôi là Chủ đề! Tên tôi là Chủ đề-4 Tôi là Chủ đề! Tên tôi là Chủ đề-3 Tôi là Chủ đề! Tên tôi là Chủ đề-2 Tôi là Chủ đề! Tên tôi là Chủ đề-1 Tôi là Chủ đề! Tên tôi là Chủ đề-5 Tôi là Chủ đề! Tên tôi là Chủ đề-6 Tôi là Chủ đề! Tên tôi là Chủ đề-8 Tôi là Chủ đề! Tên tôi là Chủ đề-9 Tôi là Chủ đề! Tên tôi là đầu ra Bảng điều khiển Thread-7 từ lần chạy thứ ba: Tôi là Thread! Tên tôi là Chủ đề-0 Tôi là Chủ đề! Tên tôi là Chủ đề-3 Tôi là Chủ đề! Tên tôi là Chủ đề-1 Tôi là Chủ đề! Tên tôi là Chủ đề-2 Tôi là Chủ đề! Tên tôi là Chủ đề-6 Tôi là Chủ đề! Tên tôi là Chủ đề-4 Tôi là Chủ đề! Tên tôi là Chủ đề-9 Tôi là Chủ đề! Tên tôi là Chủ đề-5 Tôi là Chủ đề! Tên tôi là Chủ đề-7 Tôi là Chủ đề! Tên tôi là Chủ đề-8
Các sự cố được tạo bởi đa luồng
Trong ví dụ của chúng tôi với sách, bạn đã thấy rằng đa luồng giải quyết các nhiệm vụ rất quan trọng và có thể làm cho các chương trình của chúng tôi nhanh hơn. Thường nhanh hơn nhiều lần. Nhưng đa luồng được coi là một chủ đề khó. Thật vậy, nếu sử dụng không đúng cách, nó sẽ tạo ra vấn đề thay vì giải quyết chúng. Khi tôi nói 'tạo ra vấn đề', tôi không có ý nói theo nghĩa trừu tượng nào đó. Có hai vấn đề cụ thể mà đa luồng có thể tạo ra: bế tắc và điều kiện chủng tộc. Bế tắc là tình huống trong đó nhiều luồng đang chờ tài nguyên do nhau nắm giữ và không có luồng nào có thể tiếp tục chạy. Chúng ta sẽ nói nhiều hơn về nó trong các bài học tiếp theo. Ví dụ sau đây sẽ đủ cho bây giờ:
- Thread-1 ngừng tương tác với Object-1 và chuyển sang Object-2 ngay sau khi Thread-2 ngừng tương tác với Object-2 và chuyển sang Object-1.
- Thread-2 ngừng tương tác với Object-2 và chuyển sang Object-1 ngay sau khi Thread-1 ngừng tương tác với Object-1 và chuyển sang Object-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Bây giờ hãy tưởng tượng rằng chương trình chịu trách nhiệm điều hành một robot nấu thức ăn! Thread-0 lấy trứng ra khỏi tủ lạnh. Chủ đề-1 bật bếp. Chủ đề-2 lấy một cái chảo và đặt nó lên bếp. Chủ đề-3 thắp sáng bếp. Chủ đề-4 đổ dầu vào chảo. Thread-5 đập trứng và đổ chúng vào chảo. Chủ đề-6 ném vỏ trứng vào thùng rác. Chủ đề-7 loại bỏ những quả trứng đã nấu chín khỏi đầu đốt. Chủ đề-8 đặt trứng chín vào đĩa. Chủ đề-9 rửa bát đĩa. Nhìn vào kết quả của chương trình của chúng tôi: Chủ đề được thực hiện: Chủ đề-0 Chủ đề được thực hiện: Chủ đề-2 Chủ đề được thực hiện Chủ đề-1 Chủ đề được thực hiện: Chủ đề-4 Chủ đề được thực hiện: Chủ đề-9 Chủ đề được thực hiện: Chủ đề-5 Chủ đề được thực hiện: Chủ đề-8 Chủ đề đã thực hiện: Chủ đề-7 Chủ đề đã thực hiện: Chủ đề-3 Đây có phải là một thói quen hài kịch? :) Và tất cả là do chương trình của chúng ta hoạt động phụ thuộc vào thứ tự thực hiện của các luồng. Chỉ cần vi phạm một chút trình tự bắt buộc, nhà bếp của chúng tôi biến thành địa ngục và một con rô-bốt điên loạn sẽ phá hủy mọi thứ xung quanh nó. Đây cũng là một vấn đề phổ biến trong lập trình đa luồng. Bạn sẽ nghe về nó nhiều hơn một lần. Để kết thúc bài học này, tôi muốn giới thiệu một cuốn sách về đa luồng. 
GO TO FULL VERSION