Đa luồng trong Java
Bất kỳ ứng dụng nào cũng có thể có nhiều tiến trình (thể hiện). Mỗi tiến trình này có thể được chỉ định là một luồng đơn hoặc nhiều luồng. Trong hướng dẫn này, chúng ta sẽ xem cách thực hiện nhiều tác vụ cùng lúc và cũng tìm hiểu thêm về luồng và đồng bộ hóa giữa các luồng.
Chủ đề đơn là gì?
Một chủ đề duy nhất trong Java về cơ bản là một đơn vị xử lý nhẹ và nhỏ nhất. Java sử dụng luồng bằng cách sử dụng “Lớp luồng”. Có hai loại luồng – luồng người dùng và luồng daemon (luồng daemon được sử dụng khi chúng ta muốn dọn dẹp ứng dụng và được sử dụng ở chế độ nền). Khi ứng dụng bắt đầu, luồng người dùng được tạo. Sau đó, chúng ta có thể tạo nhiều luồng người dùng và luồng daemon.
Ví dụ về chủ đề đơn:
package demotest; public class GuruThread { public static void main(String[] args) { System.out.println("Single Thread"); } }
Ưu điểm của sợi đơn:
- Giảm chi phí hoạt động trong ứng dụng khi một luồng đơn thực thi trong hệ thống
- Ngoài ra, nó còn làm giảm chi phí bảo trì ứng dụng.
Đa luồng trong Java?
Đa luồng in Java là một quá trình thực hiện hai hoặc nhiều luồng cùng lúc để tận dụng tối đa CPU. Các ứng dụng đa luồng thực hiện hai hoặc nhiều luồng chạy đồng thời. Do đó, nó cũng được gọi là Đồng thời trong Java. Mỗi luồng chạy song song với nhau. Nhiều luồng không phân bổ vùng bộ nhớ riêng biệt, do đó chúng tiết kiệm bộ nhớ. Ngoài ra, chuyển đổi ngữ cảnh giữa các luồng mất ít thời gian hơn.
Ví dụ về Đa luồng:
package demotest; public class GuruThread1 implements Runnable { public static void main(String[] args) { Thread guruThread1 = new Thread("Guru1"); Thread guruThread2 = new Thread("Guru2"); guruThread1.start(); guruThread2.start(); System.out.println("Thread names are following:"); System.out.println(guruThread1.getName()); System.out.println(guruThread2.getName()); } @Override public void run() { } }
Ưu điểm của đa luồng:
- Người dùng không bị chặn vì các luồng độc lập và đôi khi chúng tôi có thể thực hiện nhiều thao tác
- Vì các luồng này độc lập nên các luồng khác sẽ không bị ảnh hưởng nếu một luồng gặp ngoại lệ.
Vòng đời của luồng trong Java
Vòng đời của một thread:
Có nhiều giai đoạn khác nhau trong vòng đời của luồng như trong sơ đồ trên:
- Mới
- có thể chạy được
- Chạy
- Đợi
- Đã chết
- New: Trong giai đoạn này, luồng được tạo bằng lớp “Lớp luồng”. Nó vẫn ở trạng thái này cho đến khi chương trình bắt đầu sợi chỉ. Nó còn được gọi là sợi sinh ra.
- Có thể chạy được: Trong trang này, phiên bản của luồng được gọi bằng phương thức bắt đầu. Điều khiển luồng được trao cho bộ lập lịch để hoàn tất quá trình thực thi. Nó phụ thuộc vào bộ lập lịch, có chạy luồng hay không.
- Chạy: Khi luồng bắt đầu thực thi thì trạng thái sẽ được thay đổi thành trạng thái “đang chạy”. Bộ lập lịch chọn một luồng từ nhóm luồng và nó bắt đầu thực thi trong ứng dụng.
- Đang chờ đợi: Đây là trạng thái khi một luồng phải chờ. Vì có nhiều luồng đang chạy trong ứng dụng nên cần phải đồng bộ hóa giữa các luồng. Do đó, một luồng phải chờ cho đến khi luồng kia được thực thi. Do đó, trạng thái này được gọi là trạng thái chờ.
- Đã chết: Đây là trạng thái khi thread kết thúc. Luồng đang ở trạng thái chạy và ngay sau khi xử lý xong, nó sẽ ở trạng thái “chết”.
Các phương pháp đa luồng trong Java
Một số phương thức thường được sử dụng cho thread là:Phương pháp | Mô tả |
---|---|
khởi đầu() | Phương thức này bắt đầu thực thi luồng và JVM gọi phương thức run() trên luồng. |
Ngủ (int mili giây) | Phương pháp này khiến luồng ngủ, do đó quá trình thực thi của luồng sẽ tạm dừng trong vài mili giây và sau đó, luồng lại bắt đầu thực thi. Điều này giúp đồng bộ hóa các luồng. |
getName () | Nó trả về tên của chủ đề. |
setPriority(int newpriority) | Nó thay đổi mức độ ưu tiên của chủ đề. |
năng suất () | Nó khiến luồng hiện tại bị dừng và các luồng khác thực thi. |
Ví dụ: Trong chương trình đa luồng này trong Java Ví dụ, chúng ta sẽ tạo một luồng và khám phá các phương thức tích hợp có sẵn cho luồng.
package demotest; public class thread_example1 implements Runnable { @Override public void run() { } public static void main(String[] args) { Thread guruthread1 = new Thread(); guruthread1.start(); try { guruthread1.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } guruthread1.setPriority(1); int gurupriority = guruthread1.getPriority(); System.out.println(gurupriority); System.out.println("Thread Running"); } }
Giải thích mã:
- Dòng mã 2: Chúng tôi đang tạo một lớp “thread_Example1” đang triển khai giao diện Runnable (nó phải được triển khai bởi bất kỳ lớp nào có phiên bản được dự định thực thi bởi luồng.)
- Dòng mã 4: Nó ghi đè phương thức chạy của giao diện có thể chạy được vì bắt buộc phải ghi đè phương thức đó
- Dòng mã 6: Ở đây chúng ta đã xác định phương thức chính để bắt đầu thực thi luồng.
- Dòng mã 7: Ở đây, chúng tôi đang tạo một tên luồng mới là “guruthread1” bằng cách khởi tạo một lớp luồng mới.
- Dòng mã 8: chúng ta sẽ sử dụng phương thức “bắt đầu” của luồng bằng cách sử dụng phiên bản “guruthread1”. Ở đây thread sẽ bắt đầu thực thi.
- Dòng mã 10: Ở đây chúng tôi đang sử dụng phương thức “ngủ” của luồng bằng cách sử dụng phiên bản “guruthread1”. Do đó, luồng sẽ ngủ trong 1000 mili giây.
- Mã 9-14: Ở đây chúng tôi đã đặt phương thức ngủ trong khối thử bắt vì có ngoại lệ được kiểm tra xảy ra, tức là ngoại lệ bị gián đoạn.
- Dòng mã 15: Ở đây chúng tôi đang đặt mức độ ưu tiên của luồng thành 1 từ bất kỳ mức độ ưu tiên nào
- Dòng mã 16: Ở đây chúng ta đang nhận được mức độ ưu tiên của luồng bằng cách sử dụng getPriority()
- Dòng mã 17: Ở đây chúng tôi đang in giá trị được tìm nạp từ getPriority
- Dòng mã 18: Ở đây chúng tôi đang viết một văn bản mà chủ đề đang chạy.
Khi bạn thực thi đoạn mã trên, bạn sẽ nhận được kết quả sau:
Đầu ra:
5 là mức độ ưu tiên của Luồng và Chạy luồng là văn bản là đầu ra của mã của chúng tôi.
Java Sợi chỉ Syncsự hóa thân
Trong đa luồng, có hành vi không đồng bộ của các chương trình. Nếu một luồng đang ghi một số dữ liệu và một luồng khác đang đọc dữ liệu cùng lúc, có thể tạo ra sự không nhất quán trong ứng dụng. Khi có nhu cầu truy cập các tài nguyên được chia sẻ bởi hai hoặc nhiều luồng, thì phương pháp đồng bộ hóa được sử dụng. Java đã cung cấp các phương pháp đồng bộ để thực hiện hành vi đồng bộ.
Trong cách tiếp cận này, một khi luồng đạt đến bên trong khối synchronized, thì không luồng nào khác có thể gọi phương thức đó trên cùng một đối tượng. Tất cả các luồng phải đợi cho đến khi luồng đó hoàn thành khối synchronized và thoát ra khỏi khối đó. Theo cách này, đồng bộ hóa giúp ích cho ứng dụng đa luồng. Một luồng phải đợi cho đến khi luồng khác hoàn thành việc thực thi của nó, chỉ khi đó các luồng khác mới được phép thực thi.
Nó có thể được viết dưới dạng sau:
Synchronized(object) { //Block of statements to be synchronized }
Đa luồng trong Java Chương trình ví dụ
Trong đa luồng này Java Ví dụ: chúng ta sẽ lấy hai luồng và lấy tên của luồng.
Example1:
GuruThread1.java package demotest; public class GuruThread1 implements Runnable{ /** * @param args */ public static void main(String[] args) { Thread guruThread1 = new Thread("Guru1"); Thread guruThread2 = new Thread("Guru2"); guruThread1.start(); guruThread2.start(); System.out.println("Thread names are following:"); System.out.println(guruThread1.getName()); System.out.println(guruThread2.getName()); } @Override public void run() { } }
Giải thích mã:
- Dòng mã 3: Chúng tôi đã chọn một lớp “GuruThread1” để triển khai Runnable (nó phải được triển khai bởi bất kỳ lớp nào có phiên bản được dự định thực thi bởi luồng.)
- Dòng mã 8: Đây là phương thức chính của lớp
- Dòng mã 9: Ở đây chúng ta đang khởi tạo lớp Thread và tạo một cá thể có tên là “guruThread1” và tạo một luồng.
- Dòng mã 10: Ở đây chúng ta đang khởi tạo lớp Thread và tạo một cá thể có tên là “guruThread2” và tạo một luồng.
- Dòng mã 11: Chúng tôi đang bắt đầu chủ đề tức là guruThread1.
- Dòng mã 12: Chúng tôi đang bắt đầu chủ đề tức là guruThread2.
- Dòng mã 13: Xuất văn bản dưới dạng “Tên chủ đề đang theo dõi:”
- Dòng mã 14: Lấy tên của luồng 1 bằng phương thức getName() của lớp luồng.
- Dòng mã 15: Lấy tên của luồng 2 bằng phương thức getName() của lớp luồng.
Khi bạn thực thi đoạn mã trên, bạn sẽ nhận được kết quả sau:
Đầu ra:
Tên chủ đề đang được xuất ra ở đây dưới dạng
- Guru1
- Guru2
Ví dụ 2:
Trong đa luồng này trong Java Ví dụ, chúng ta sẽ tìm hiểu về ghi đè các phương thức run() và start() của một giao diện có thể chạy được và tạo hai luồng của lớp đó và chạy chúng tương ứng.
Ngoài ra, chúng tôi đang học hai lớp,
- Một cái sẽ triển khai giao diện có thể chạy được và
- Một cái khác sẽ có phương thức chính và thực hiện tương ứng.
package demotest; public class GuruThread2 { public static void main(String[] args) { // TODO Auto-generated method stub GuruThread3 threadguru1 = new GuruThread3("guru1"); threadguru1.start(); GuruThread3 threadguru2 = new GuruThread3("guru2"); threadguru2.start(); } } class GuruThread3 implements Runnable { Thread guruthread; private String guruname; GuruThread3(String name) { guruname = name; } @Override public void run() { System.out.println("Thread running" + guruname); for (int i = 0; i < 4; i++) { System.out.println(i); System.out.println(guruname); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Thread has been interrupted"); } } } public void start() { System.out.println("Thread started"); if (guruthread == null) { guruthread = new Thread(this, guruname); guruthread.start(); } } }
Giải thích mã:
- Dòng mã 2: Ở đây chúng ta đang tham gia một lớp “GuruThread2” sẽ có phương thức chính trong đó.
- Dòng mã 4: Ở đây chúng ta đang sử dụng một phương thức chính của lớp.
- Dòng mã 6-7: Ở đây, chúng tôi đang tạo một phiên bản của lớp GuruThread3 (được tạo ở các dòng mã bên dưới) là “threadguru1” và chúng tôi đang bắt đầu chuỗi.
- Dòng mã 8-9: Ở đây, chúng tôi đang tạo một phiên bản khác của lớp GuruThread3 (được tạo ở các dòng mã bên dưới) là “threadguru2” và chúng tôi đang bắt đầu chuỗi.
- Dòng mã 11: Ở đây chúng ta đang tạo một lớp “GuruThread3” đang triển khai giao diện có thể chạy được (nó phải được triển khai bởi bất kỳ lớp nào có phiên bản được dự định thực thi bởi luồng.)
- Dòng mã 13-14: chúng ta đang lấy hai biến lớp, trong đó một biến thuộc loại lớp luồng và biến kia thuộc lớp chuỗi.
- Dòng mã 15-18: chúng ta đang ghi đè hàm tạo GuruThread3, hàm này lấy một đối số làm kiểu chuỗi (là tên luồng) được gán cho biến lớp guruname và do đó tên của luồng được lưu trữ.
- Dòng mã 20: Ở đây chúng ta ghi đè phương thức run() của giao diện có thể chạy được.
- Dòng mã 21: Chúng tôi đang xuất tên luồng bằng câu lệnh println.
- Dòng mã 22-31: Ở đây chúng ta đang sử dụng vòng lặp for với bộ đếm được khởi tạo bằng 0 và nó không được nhỏ hơn 4 (chúng ta có thể lấy bất kỳ số nào do đó vòng lặp ở đây sẽ chạy 4 lần) và tăng bộ đếm. Chúng tôi đang in tên luồng và cũng làm cho luồng ở chế độ ngủ trong 1000 mili giây trong khối thử bắt khi phương thức ngủ đưa ra ngoại lệ đã kiểm tra.
- Dòng mã 33: Ở đây chúng tôi ghi đè phương thức bắt đầu của giao diện có thể chạy được.
- Dòng mã 35: Chúng tôi đang xuất ra dòng chữ “Chủ đề đã bắt đầu”.
- Dòng mã 36-40: Ở đây chúng ta đang sử dụng một điều kiện if để kiểm tra xem biến lớp guruthread có giá trị trong đó hay không. Nếu nó rỗng thì chúng ta đang tạo một cá thể bằng cách sử dụng lớp luồng lấy tên làm tham số (giá trị được gán trong hàm tạo). Sau đó, luồng được bắt đầu bằng phương thức start().
Khi bạn thực thi đoạn mã trên, bạn sẽ nhận được kết quả sau:
Đầu ra:
Do đó, có hai chủ đề, chúng tôi nhận được thông báo hai lần “Chủ đề đã bắt đầu”.
Chúng tôi nhận được tên của chủ đề khi chúng tôi xuất chúng.
Nó đi vào vòng lặp nơi chúng ta đang in bộ đếm và tên luồng và bộ đếm bắt đầu bằng 0.
Vòng lặp thực hiện ba lần và ở giữa luồng được ngủ trong 1000 mili giây.
Do đó, đầu tiên, chúng ta nhận được guru1 rồi guru2 rồi lại guru2 vì luồng ngủ ở đây trong 1000 mili giây rồi tiếp theo là guru1 và lại guru1, luồng ngủ trong 1000 mili giây, vì vậy chúng ta nhận được guru2 và sau đó là guru1.
Tổng kết
Trong hướng dẫn này, chúng ta đã thấy các ứng dụng đa luồng trong Java và cách sử dụng luồng đơn và đa luồng trong Java.
- Giải thích đa luồng trong Java: trong đa luồng, người dùng không bị chặn vì các luồng độc lập và có thể thực hiện nhiều thao tác cùng lúc
- Các giai đoạn khác nhau của vòng đời của luồng là,
- Mới
- có thể chạy được
- Chạy
- Đợi
- Đã chết
- Chúng tôi cũng đã tìm hiểu về đồng bộ hóa giữa các luồng, giúp ứng dụng chạy trơn tru.
- Lập trình đa luồng trong Java làm cho nhiều tác vụ ứng dụng trở nên dễ dàng hơn.