Java - Đa luồng
Java là một ngôn ngữ lập trình đa luồng có nghĩa là chúng ta có thể phát triển chương trình đa luồng bằng Java. Một chương trình đa luồng chứa hai hoặc nhiều phần có thể chạy đồng thời và mỗi phần có thể xử lý một tác vụ khác nhau đồng thời sử dụng tối ưu các tài nguyên có sẵn, đặc biệt khi máy tính của bạn có nhiều CPU.
Theo định nghĩa, đa nhiệm là khi nhiều quy trình chia sẻ các tài nguyên xử lý chung như CPU. Đa luồng mở rộng ý tưởng về đa nhiệm vào các ứng dụng nơi bạn có thể chia nhỏ các hoạt động cụ thể trong một ứng dụng thành các luồng riêng lẻ. Mỗi luồng có thể chạy song song. Hệ điều hành phân chia thời gian xử lý không chỉ giữa các ứng dụng khác nhau mà còn giữa từng luồng trong một ứng dụng.
Đa luồng cho phép bạn viết theo cách mà nhiều hoạt động có thể tiến hành đồng thời trong cùng một chương trình.
Vòng đời của một sợi chỉ
Một luồng trải qua nhiều giai đoạn khác nhau trong vòng đời của nó. Ví dụ, một luồng được sinh ra, bắt đầu, chạy và sau đó chết. Sơ đồ sau đây cho thấy toàn bộ vòng đời của một luồng.
Sau đây là các giai đoạn của vòng đời:
New- Một luồng mới bắt đầu vòng đời của nó ở trạng thái mới. Nó vẫn ở trạng thái này cho đến khi chương trình bắt đầu luồng. Nó cũng được gọi làborn thread.
Runnable- Sau khi một luồng mới sinh được bắt đầu, luồng đó có thể chạy được. Một luồng ở trạng thái này được coi là đang thực thi nhiệm vụ của nó.
Waiting- Đôi khi, một luồng chuyển sang trạng thái chờ trong khi luồng đó đợi một luồng khác thực hiện một tác vụ. Một luồng chỉ chuyển trở lại trạng thái chạy được khi một luồng khác báo hiệu luồng đang chờ tiếp tục thực thi.
Timed Waiting- Một luồng có thể chạy được có thể vào trạng thái chờ được hẹn giờ trong một khoảng thời gian nhất định. Một luồng ở trạng thái này chuyển trở lại trạng thái có thể chạy khi khoảng thời gian đó hết hạn hoặc khi sự kiện nó đang chờ xảy ra.
Terminated (Dead) - Một luồng có thể chạy đi vào trạng thái kết thúc khi nó hoàn thành nhiệm vụ của mình hoặc kết thúc bằng cách khác.
Ưu tiên chuỗi
Mọi luồng Java đều có một mức độ ưu tiên giúp hệ điều hành xác định thứ tự mà các luồng được lên lịch.
Mức độ ưu tiên của luồng Java nằm trong phạm vi giữa MIN_PRIORITY (hằng số 1) và MAX_PRIORITY (hằng số 10). Theo mặc định, mọi luồng được ưu tiên NORM_PRIORITY (hằng số 5).
Các luồng có mức ưu tiên cao hơn quan trọng hơn đối với một chương trình và phải được phân bổ thời gian xử lý trước các luồng có mức ưu tiên thấp hơn. Tuy nhiên, các ưu tiên của luồng không thể đảm bảo thứ tự mà các luồng thực thi và phụ thuộc rất nhiều vào nền tảng.
Tạo một chuỗi bằng cách triển khai một giao diện có thể chạy được
Nếu lớp của bạn dự định được thực thi dưới dạng một luồng thì bạn có thể đạt được điều này bằng cách triển khai Runnablegiao diện. Bạn sẽ cần làm theo ba bước cơ bản -
Bước 1
Bước đầu tiên, bạn cần triển khai phương thức run () được cung cấp bởi Runnablegiao diện. Phương thức này cung cấp một điểm vào cho luồng và bạn sẽ đưa logic nghiệp vụ hoàn chỉnh của mình vào bên trong phương pháp này. Sau đây là một cú pháp đơn giản của phương thức run ():
public void run( )
Bước 2
Bước thứ hai, bạn sẽ khởi tạo một Thread đối tượng sử dụng hàm tạo sau:
Thread(Runnable threadObj, String threadName);
Trong đó, threadObj là một thể hiện của một lớp thực hiệnRunnable giao diện và threadName là tên được đặt cho chủ đề mới.
Bước 3
Khi một đối tượng Thread được tạo, bạn có thể bắt đầu nó bằng cách gọi start()phương thức thực thi một cuộc gọi đến phương thức run (). Sau đây là một cú pháp đơn giản của phương thức start ():
void start();
Thí dụ
Đây là một ví dụ tạo một chuỗi mới và bắt đầu chạy nó -
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
Điều này sẽ tạo ra kết quả sau:
Đầu ra
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Tạo một chủ đề bằng cách mở rộng một lớp chủ đề
Cách thứ hai để tạo một luồng là tạo một lớp mới mở rộng Threadlớp bằng hai bước đơn giản sau. Cách tiếp cận này cung cấp tính linh hoạt hơn trong việc xử lý nhiều luồng được tạo bằng các phương thức có sẵn trong lớp Thread.
Bước 1
Bạn sẽ cần ghi đè run( )phương thức có sẵn trong lớp Thread. Phương thức này cung cấp một điểm vào cho luồng và bạn sẽ đưa logic nghiệp vụ hoàn chỉnh của mình vào bên trong phương pháp này. Sau đây là một cú pháp đơn giản của phương thức run ():
public void run( )
Bước 2
Sau khi đối tượng Thread được tạo, bạn có thể bắt đầu nó bằng cách gọi start()phương thức thực thi một cuộc gọi đến phương thức run (). Sau đây là một cú pháp đơn giản của phương thức start ():
void start( );
Thí dụ
Đây là chương trình trước đó được viết lại để mở rộng Chủ đề -
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
Điều này sẽ tạo ra kết quả sau:
Đầu ra
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Phương thức chủ đề
Sau đây là danh sách các phương thức quan trọng có sẵn trong lớp Thread.
Sr.No. | Phương pháp & Mô tả |
---|---|
1 | public void start() Bắt đầu luồng trong một đường dẫn thực thi riêng biệt, sau đó gọi phương thức run () trên đối tượng Luồng này. |
2 | public void run() Nếu đối tượng Thread này được khởi tạo bằng cách sử dụng đích Runnable riêng, thì phương thức run () được gọi trên đối tượng Runnable đó. |
3 | public final void setName(String name) Thay đổi tên của đối tượng Thread. Ngoài ra còn có một phương thức getName () để lấy tên. |
4 | public final void setPriority(int priority) Đặt mức độ ưu tiên của đối tượng Thread này. Các giá trị có thể là từ 1 đến 10. |
5 | public final void setDaemon(boolean on) Một tham số true biểu thị Luồng này là một luồng daemon. |
6 | public final void join(long millisec) Luồng hiện tại gọi phương thức này trên luồng thứ hai, khiến luồng hiện tại bị chặn cho đến khi luồng thứ hai kết thúc hoặc hết số mili giây được chỉ định. |
7 | public void interrupt() Làm gián đoạn chuỗi này, khiến nó tiếp tục thực thi nếu nó bị chặn vì bất kỳ lý do gì. |
số 8 | public final boolean isAlive() Trả về true nếu luồng còn sống, bất kỳ thời điểm nào sau khi luồng được bắt đầu nhưng trước khi nó chạy đến khi hoàn thành. |
Các phương thức trước đó được gọi trên một đối tượng Thread cụ thể. Các phương thức sau trong lớp Thread là tĩnh. Việc gọi một trong các phương thức tĩnh thực hiện thao tác trên luồng hiện đang chạy.
Sr.No. | Phương pháp & Mô tả |
---|---|
1 | public static void yield() Làm cho luồng hiện đang chạy nhường cho bất kỳ luồng nào khác có cùng mức độ ưu tiên đang chờ được lên lịch. |
2 | public static void sleep(long millisec) Làm cho chuỗi hiện đang chạy bị chặn trong ít nhất số mili giây được chỉ định. |
3 | public static boolean holdsLock(Object x) Trả về true nếu luồng hiện tại giữ khóa trên Đối tượng đã cho. |
4 | public static Thread currentThread() Trả về một tham chiếu đến luồng hiện đang chạy, là luồng gọi phương thức này. |
5 | public static void dumpStack() In dấu vết ngăn xếp cho luồng hiện đang chạy, rất hữu ích khi gỡ lỗi một ứng dụng đa luồng. |
Thí dụ
Chương trình ThreadClassDemo sau đây trình bày một số phương thức này của lớp Thread. Xem xét một lớp họcDisplayMessage cái nào thực hiện Runnable -
// File Name : DisplayMessage.java
// Create a thread to implement Runnable
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) {
this.message = message;
}
public void run() {
while(true) {
System.out.println(message);
}
}
}
Sau đây là một lớp khác mở rộng lớp Thread:
// File Name : GuessANumber.java
// Create a thread to extentd Thread
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) {
this.number = number;
}
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
} while(guess != number);
System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
}
}
Sau đây là chương trình chính, sử dụng các lớp được định nghĩa ở trên -
// File Name : ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String [] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
Điều này sẽ tạo ra kết quả sau. Bạn có thể thử lại ví dụ này và lần nào bạn cũng sẽ nhận được một kết quả khác.
Đầu ra
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......
Các khái niệm đa luồng chính của Java
Trong khi thực hiện lập trình Đa luồng trong Java, bạn sẽ cần phải có các khái niệm sau rất hữu ích:
Đồng bộ hóa luồng là gì?
Xử lý giao tiếp giữa các luồng
Xử lý bế tắc luồng
Các hoạt động chính của chuỗi