Tiến trình đa nhiệm và đa luồng

1. Các luồng trong Java

Như trên đã nêu, một luồng là một mạch thực hiện trong một chương trình (một tiến trình) và nó có thể thực hiện riêng biệt. Ở thời điểm thực hiện, các luồng cùa một chưong trình có thể cùng sừ dụng chung một không gian bộ nhó, vì vậy có thể chia sẻ VỚI nhau về dữ liệu và mã lệnh. Chúng cũng có thể cùng chia sè với nhau trong một tiến trình để thực thi một chuông trình.

Các luồng trong Java được phép thực thi đồng thời mà không cần đồng bộ, nghĩa là nhiều tác vụ khác nhau có thể thực hiện đồng thời. VI phần lớn các máy tính là đơn bộ xứ lý CPU, nên máy ảo Java (JVM) sù dụng cơ chế chia sè thời gian và cho phép mỗi luồng có cơ hội thực hiện một khoảng thời gian ngắn sau đó nhường quyền điều khiển cho những luồng khác.

Để tận dụng được những khả năng trong mẫu hỉnh lập trình đa luồng cùa Java, cần phải hiểu rõ các phương diện sau:

  • Tạo lập các luồng,
  • Bời vì các luồng có thể chia sẻ với nhau cùng một không gian bộ nhớ, nên việc đồng bộ hoá để truy nhập vào dữ liệu, mã chung là rất quan trọng,
  • Vì các luồng có thể ờ những trạng thái khác nhau, do vậy, cần phải hiểu rõ sự chuyển đồi giữa các trạng thái như thế nào.

Tạo lập các luồng

Java sù dụng một cơ chế trong đó mỗi luồng có một cơ hội để chạy trong một thời khoảng tương đối nhỏ và ngay sau đó lại kích hoạt luồng khác thực hiện.

Java cung cấp hai giải pháp tạo lập luồng:

  1. Thiết lập lớp con cúa Thread
  2. Cài đặt lóp xừ lý luồng từ giao diện

(í) Cách thứ nhất: Trong Java có một lóp được xây dụng sẵn là Thread, lớp cơ sờ để xây dựng những lớp mới kế thừa nhằm tạo các luồng thực hiện theo yêu cầu.

Ví dụ, lớp MyClass được mở rộng dựa trên cơ sở kế thừa lóp Thread nhằm tạo ra các luồng thực hiện. Các tiến trình trong hệ thống bắt đầu thực hiện ở một địa chỉ đặc biệt được xác định bởi hàm có tên là main (). Tương tự khi một luồng của lớp MyClass được tạo ra thì nó gọi hàm run () để thực hiện. Hàm này được viết đè để thực thi những công việc yèu cầu trong mỗi luồng được tạo ra.

class MyClass extends Thread!

// Một số thuộc tính public void run (){

// Các lệnh cần thực hiện theo luồng

}

// Một số hàm khác được viết đè hay được bổ sung

}

Khi chương trình chạy nó sẽ gọi một hàm đặc biệt đã được khai báo trong Thread, đó là start () để bắt đầu một luồng đã được tạo ra

Ví dụ: Tạo ra hai luồng thực hiện đồng thời để hiển thị các từ trong dãy (“Hôm nay”, “báo cáo”, “bài tập”, “lớn,”, “môn học”, “lập trinh Java “) lên màn hình.

Chương trình này tạo ra hai luồng: threadl và thread2 từ lớp MyThread. Sau đó nó khởi động cả hai luồng và thực hiện một chu trình lạp do để đợi cho đến khi các luồng kết thúc hoặc “chết”. Hai luồng này hiển thị lẩn lượt những dòng chữ “Hôm nay”, “báo cáo”, “bài tập”, “lớn,”, “môn học”, “lập trình Java.” sau khi chò một khoảng thòi gian ngắn ngẫu nhiên giữa các lần thực hiện. Bởi vỉ cà hai luồng cùng hiển thị lên màn hình nên chương trình phải xác định luồng nào có thể được hiển thị thông tin trên màn hình tại những thời điểm khác nhau trong khi chương trình thực hiện.

import java.lang.Thread;

import java,lang.System;

import java,lang.InterruptedExceptìon;

class ThreadTestl {

public static void main(String args[]){

Mythread threadl = new Mythread(“Thread 1:”);

Mythread thread2 = new Mythread(“Thread 2:”);

threadl.start();

thread2.start();

boolean threadlIsAlive = true;

boolean thread2IsAlive = true;

do {

if(threadlIsAlive && !threadl.IsAlive()){ threadllsAlive = false;

System.out.println(“Thread 1 is dead.”);

}

if(thread2IsAlìve && !thread2.isAlìve()){ thread2IsAlive = false;

System.out.printIn(“Thread 2 is dead.”);

}

} while (threadlIsAlìve II thread2IsAlive);

}

}

class Mythread extends Thread {

static String message [ ] ={“Hôm nay”, “báo cáo”, “bài
tập”, “lớn,”, “mòn học”, “lập trình Java.”};

public Mythread (String id){ super(id);

}

public void run () {

String name = getName();

for(int i=0; i < message.length; + + i){

randomWait();

System.out.println(name + message[i]);

}

}

void randomWait(){ try {

sleep ( (long) (1000*Math.random()));

}catch (InterruptedException x){

System.out.println(“Interruped”);

}

}

)

Chương trình trên mỗi khi thực thi sẽ cho kết quả khác nhau. Sờ đĩ như vậy là vì chương trình sứ dựng bộ đếm số ngẫu nhiên để xác định một ỉuồng sẽ đợi trong thời gian ngẫu nhiên trước khi hiền thị thông báo lên màn hình. Sau đây là kết quả trong một lần chạy thừ :

Thread 1: Hôm nay Thread 2: Hõm nay

Kết quả trên cho thấy luồng threadl thực hiện trước và hiển thi dòng chữ “Hôm này’’ lên màn hình, sau đó đợi để đuợc thi hành tiếp ừong khi threac!2 đang hiển thị dòng chữ “Hôm nay”. Khi đến lượt, luồng threadl hiển thị tiếp dòng chữ “báo cáo”, rồi lại nhường cho thread2 để hiển thị tiếp dòng chữ “báo cáo” và sau đó là “bài tập”. Luân phiên thực hiện như vậy cho đến khi các luồng kết thúc.

Lóp ThreadTestl có một hàm main(). Hàm này tạo ra hai đổi tượng mới là threadl và thread2 của lóp Mythread Sau đó hàm main () khởi động cả hai luồng trên bằng hàm start ().

Lưu ỷ: Java không hỗ trợ đa kế thừa. Do vậy, nếu người lập trinh muốn tạo ra một lớp kế thừa từ một lóp cơ sờ và để thực hiện được theo luồng thì nó cũng đồng thời phải kế thừa từ lớp Thread Điều này không thực hiện được. Do vậy, ta phải thực hiện theo cách thứ hai.

(li) Cách thứ hai: Java giải quyết hạn chế trên bằng cách xây dụng lóp trên cơ sờ cài đặt giao diện luồng Runnable để tạo ra các luồng thực hiện. Người lạp trinh thiết kể các lớp thực hiện theo luồng bằng cách cài đặt theo giao diện Runnable như sau.

class MỵClass implements Runnable!

// Các thuộc tính

// Nạp chồng hay viết đè một số hàm

// Viết đè hàm run()

}

Hàm main () của lớp ThreadTest2 khác với hàm main () cùa ThreadTestl ỡ chỗ tạo ra threadl và thread2. Lóp ThreadTestl tạo ra luồng là một thực thể mới cùa lớp Mythread Còn ThreadTest2 thì không tạo ra luồng một cách trực tiếp bời vi lớp MỵClass không phải là lớp con của lóp Thread Vì vậy, trước tiên lóp ThreadTest2 tạo ra hai đối tượng của lớp MỵClass và sau đó chuyển chủng cho Thread 0 để tạo lập các luồng cùa lóp Thread Hàm tạo dựng Thread!) được lớp ThreadTest2 sừ dụng với đổi số là đối tượng của bất kỳ lớp nào cài đặt giao diện Runnable. Phần còn lại của hàm main () trong lóp ThreadTest2tương tự như Threadiest1.

Hàm run () của hai lớp ThreadTestl và ThreadTest2 gần như giống nhau, chi khác ờ tên gọi.

2. Các trạng thái của Thread

Một luồng có thể ở một trong các trạng thái sau: ([3], [6])

■ New: Khi một luồng mới được tạo ra với toán từ now() và sẵn sàng hoạt động.

  • Runnable: Trạng thái mà luồng đang chiếm CPU để thực hiện, khi bắt đầu thì nó gọi hàm start (). Bộ lập lịch phân luồng của hệ điều hành sẽ quyết định luồng nào sẽ được chuyển về trạng thái Runnable và hoạt động. Cũng cần lưu ý rằng ở một thời điểm, một luồng ò trạng thái Runnable có thể hoặc không thể thực hiện.
  • Non runnable (blocked) : Từ trạng thái Runnable chuyển sang trạng thái ngừng thực hiện (“bị chặn”) khi gọi một trong các hàm: sleep 0, suspend 0, wait (), hay bị chặn lại ởInput/output Trong trạng thái bị chặn có ba trạng thái con:
  • Waiting: khiờừạngthái Runnable, một luồng thực hiện hàm wait 0 thl nó sẽ chuyển sang trạng thái chờ đợi (Waiting) .
  • Sleeping: khi ờ trạng thái Runnable, một luồng thực hiện hàm sleep 0 thì nó sẽ chuyển sang trạng thái ngủ (Sleeping) .
  • Blocked: khi ờ trạng thái Runnable, một luồng bị chặn lại bời những yêu cầu về tài nguyên, như yêu cầu vào/ra (I/O), thì nó sẽ chuyển sang trạng bị chặn (Blocked) .

Mỗi luồng phải thoát ra khỏi trạng thái Blocked để quay về trạng thái Runnable, khi

  • Nếu một luồng đã được cho đi “ngủ” (gọi sleep (n)) sau khoảng thời gian n micro giây.
  • Nếu một luồng bị chặn lại vì vào/ra và quá trình này đã kết thúc.
  • Nếu luồng bị chặn lại khi gọi hàm wait (), sau đó được thông báo tiếp tục bằng cách gọi hàm notify () hoặc notifyAll () .
  • Nếu một luồng bị chặn lại để chờ monitor cùa đối tượng đang bị chiếm giữ bởi luồng khác, khi monitor đó được giải phóng thì luồng bị chặn này có thể tiếp tục thực hiện (khái niệm monitor được đề cập ở phần sau).
  • Nếu một luồng bị chặn lại bời lời gọi hàm suspendO, muốn thực hiện thì nước đó phải gọi hàm resume () .

Nếu ta gọi các hàm không phù hợp đối với các luồng thì JVM sẽ phát sinh ra ngoại lệ

IllegalThreadStateException.

  • Dead’ Luồng chuyến sang trạng thái “chết” khi nó kết thúc hoạt động bình thường, hoặc gặp phải ngoại lệ không thực hiện tiếp được. Trong trường hợp đặc biệt, bạn có thể gọi hàm stop () để kết thúc (“giết chết”) một luồng.