CodeGym /Blog Java /Ngẫu nhiên /Đa luồng trong Java

Đa luồng trong Java

Xuất bản trong nhóm
CHÀO! Trước hết, xin chúc mừng: bạn đã đạt đến chủ đề Đa luồng trong Java! Đây là một thành tích nghiêm túc — bạn đã đi một chặng đường dài. Nhưng hãy chuẩn bị tinh thần: đây là một trong những chủ đề khó nhất trong khóa học. Và không phải là chúng ta đang sử dụng các lớp phức tạp hay nhiều phương thức ở đây: trên thực tế, chúng ta sẽ sử dụng ít hơn 20 phương thức. Hơn nữa, bạn sẽ cần thay đổi cách suy nghĩ một chút. Trước đây, các chương trình của bạn đã được thực hiện tuần tự. Một số dòng mã xuất hiện sau những dòng mã khác, một số phương thức xuất hiện sau những dòng mã khác và mọi thứ về cơ bản đều rõ ràng. Đầu tiên, chúng tôi tính toán một cái gì đó, sau đó hiển thị kết quả trên bảng điều khiển và sau đó chương trình kết thúc. Để hiểu đa luồng, tốt hơn là nghĩ về tính song song. Hãy bắt đầu với một cái gì đó khá đơn giản: ) Hãy tưởng tượng gia đình bạn đang chuyển từ nhà này sang nhà khác. Thu thập tất cả các cuốn sách của bạn sẽ là một phần quan trọng của việc di chuyển. Bạn đã tích lũy được rất nhiều sách và bạn cần đặt chúng vào hộp. Hiện tại, bạn là người duy nhất có sẵn. Mẹ đang chuẩn bị thức ăn, anh trai đang đóng gói quần áo, và em gái đã đi đến cửa hàng. Một mình, bạn có thể quản lý bằng cách nào đó. Sớm hay muộn, bạn sẽ tự mình hoàn thành nhiệm vụ, nhưng sẽ mất rất nhiều thời gian. Tuy nhiên, em gái của bạn sẽ trở lại từ cửa hàng sau 20 phút nữa và cô ấy không có việc gì khác để làm. Vì vậy, cô ấy có thể tham gia với bạn. Nhiệm vụ không thay đổi: xếp sách vào hộp. Nhưng nó đang được thực hiện nhanh gấp đôi. Tại sao? Vì công việc đang diễn ra song song. Hai 'sợi dây' khác nhau (bạn và em gái của bạn) đang thực hiện cùng một nhiệm vụ đồng thời. Và nếu không có gì thay đổi, thì sẽ có sự chênh lệch rất lớn về thời gian so với trường hợp bạn tự làm mọi việc một mình. Nếu anh trai hoàn thành công việc sớm, anh ấy có thể giúp bạn và mọi thứ sẽ diễn ra nhanh hơn.

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:
  1. 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!

    Đa luồng trong Java: nó là gì, lợi ích của nó và những cạm bẫy phổ biến - 3

    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 :)

  2. 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.

Nhưng như bạn đã đọc trong bài học này, các hệ thống ngày nay rất thông minh và ngay cả trên một lõi máy tính cũng có thể đạt được tính song song, hay đúng hơn là giả song song, trong đó các tác vụ được thực hiện luân phiên. Hãy chuyển những điều chung chung sang những điều cụ thể và tìm hiểu về lớp quan trọng nhất trong thư viện đa luồng Java — java.lang.Thread. Nói một cách chính xác, các luồng Java được biểu diễn bằng các thể hiện của lớp Thread . Điều này có nghĩa là để tạo và chạy 10 luồng, bạn cần 10 phiên bản của lớp này. Hãy viết ví dụ đơn giản nhất:

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ờ: Đa luồng trong Java: nó là gì, lợi ích của nó và những cạm bẫy phổ biến - 4Hãy tưởng tượng rằng Chủ đề-1 tương tác với một số Đối tượng-1 và Chủ đề-2 tương tác với Đối tượng-2. Hơn nữa, chương trình được viết sao cho:
  1. 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.
  2. 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.
Ngay cả khi không có hiểu biết sâu về đa luồng, bạn có thể dễ dàng thấy rằng sẽ không có gì xảy ra. Các sợi chỉ sẽ không bao giờ đổi chỗ và sẽ đợi nhau mãi mãi. Lỗi có vẻ rõ ràng, nhưng thực tế không phải vậy. Bạn có thể dễ dàng làm điều này trong một chương trình. Chúng ta sẽ xem xét các ví dụ về mã gây ra bế tắc trong các bài học tiếp theo. Nhân tiện, Quora có một ví dụ thực tế tuyệt vời giải thích bế tắc là gìlà. 'Ở một số bang ở Ấn Độ, họ sẽ không bán cho bạn đất nông nghiệp trừ khi bạn là nông dân đã đăng ký. Tuy nhiên, họ sẽ không đăng ký bạn là nông dân nếu bạn không sở hữu đất nông nghiệp'. Tuyệt vời! Chúng ta có thể nói gì?! :) Bây giờ hãy nói về điều kiện cuộc đua. Điều kiện tương tranh là lỗi thiết kế trong một hệ thống hoặc ứng dụng đa luồng, trong đó hoạt động của hệ thống hoặc ứng dụng phụ thuộc vào thứ tự thực thi các phần của mã. Hãy nhớ rằng, ví dụ của chúng tôi nơi chúng tôi bắt đầu chủ đề:

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. Đa luồng trong Java: nó là gì, lợi ích của nó và những cạm bẫy phổ biến - 6'Thực hành đồng thời Java' được viết vào năm 2006, nhưng vẫn không mất đi tính liên quan của nó. Nó dành riêng cho lập trình Java đa luồng — từ những điều cơ bản đến những lỗi và phản mẫu phổ biến nhất. Nếu một ngày nào đó bạn quyết định trở thành một bậc thầy về đa luồng, thì cuốn sách này là cuốn sách phải đọc. Hẹn gặp lại các bạn trong các bài học tiếp theo! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION