Đa luồng trong Java (Multithreading in Java) - Phần 2: Trạng thái của luồng và phương thức suspend, resume, stop, sleep



Đa luồng trong java (Multithreading in java) - Phần 2
Nội dung trong phần này:
  • Các trạng thái của luồng

  • Các trạng thái luồng do Java định nghĩa

  • Các phương thức suspend, resume , sleep, stop , destroy, isAlive, join, yeild.
1. Các trạng thái của luồng
Sơ đồ dưới đây mô tả tổng quan về các trạng thái mà luồng có thề có trong suốt vòng đời của mình và quá trình chuyển tiếp giữa các trạng thái khác nhau.

Các trạng thái của luồng

New Thread: Đây là trạng thái khi luồng vừa được khởi tạo bằng phương thức khởi tạo của lớp Thread nhưng chưa được start(). Ở trạng thái này, luồng được tạo ra nhưng chưa được cấp phát tài nguyên và cũng chưa chạy. Nếu luồng đang ở trạng thái này mà ta gọi các phương thức ép buộc stop,resume,suspend … sẽ là nguyên nhân sảy ra ngoại lệ IllegalThreadStateException .
VD:
PHP Code:
Thread test=new Thread();

Lúc này thread test đang ở trạng thái New Thread và chưa được cấp phát tài nguyên.

Runnable:
Xét đoạn mã sau:

PHP Code:

Thread test=new Thread();
test.start();

Sau khi gọi phương thức start() thì luồng test đã được cấp phát tài nguyên và các lịch điều phối CPU cho luồng test cũng bắt đầu có hiệu lực. Ở đây, chúng ta dùng trạng thái là Runnable chứ không phải Running, vì như đã nói ở phần đầu (Các mô hình đa luồng) thì luồng không thực sự luôn chạy mà tùy vào hệ thống mà có sự điều phối CPU khác nhau.

Ví dụ: Trong HĐH đa nhiệm nhưng chỉ có 1 bộ xử lý (CPU) thì không phải tất cả các tiến trình, các luồng đều được cùng chạy song song mà chúng được luân phiên nhau chạy định kỳ trong 1 thời gian nhất định, sau đó phải tạm ngưng và trả CPU về cho HĐH, HĐH lại cấp CPU này cho luồng khác chạy trong 1 khoảng nhất định ….

Như vậy: Tuy luồng đang ở trạng thái Runnable nhưng không phải luồng luôn đang chạy nên không thể gọi là Running. Tuy nhiên để đơn giản vấn đề, chúng ta có thể tạm coi trạng thái này là luồng đang “chạy” tuy rằng điều này không chính xác 100%.

Luồng sẽ ở trạng thái Runnable này từ khi luồng được gọi phương thức start() và trước khi luồng kết thúc các công việc trong phương thức run() hoặc trước khi sảy ra 1 ngoại lệ nào đó trong run() khiến run() bị kết thúc. Một dố trường hợp nếu ta tự ý stop() hoặc destroy() luồng thì cũng khiến luồng rời khỏi trạng thái runnable

Not Runnable:
Một luồng bị rơi vào trạng thái “Not Runable” thì do 1 trong bốn nguyên nhân sau:
+ Luồng bị tạm ngưng 1 khoảng nhất định do bị gọi phương thức sleep(long milisecond)
+ Luồng bị tạm ngưng cấp CPU do bị gọi phương thức suspend()
+ Luồng bị gọi phương thức wait() để tạm ngưng để chờ 1 điều kiện nào đó thay đổi khác với hiện tại. VD: chờ tài nguyên (Xem tiếp phần Đồng bộ hóa)
+ Luồng đang bị Block vì phải chờ I/O. Ví dụ: trong lúc chờ người dùng nhập dữ liệu vào bàn phím, luồng bị Block

Ví dụ:

PHP Code:

try {
Thread.sleep(10000);
} catch (InterruptedException e){
}

Trong ví dụ trên, luồng sẽ tạm ngưng làm việc trong suốt 10000 mili giây (10 giây). Tuy nhiên, cần lưu ý 1 điều là: khác với suspend(), khi sleep() luồng vẫn được cấp CPU, nhưng luồng không làm gì cả, còn suspend() luồng sẽ bị ngưng cấp CPU. Ngoài ra, cũng có 1 điều lưu ý khác : Luồng bị “Not Runnable” bởi nguyên nhân nào thì phải cho luồng chạy lại bởi 1 tác động thích hợp với đúng nguyên nhân đó. Cụ thể như sau:
+ Nếu luồng bị Sleep, phải đợi cho đến khi hết thời gian chỉ định sleep thì luồng sẽ tự chạy lại
+ Nếu luồng bị suspend() ta phải gọi phương thức resume() để tiếp tục lại
+ Nếu luồng đang wait() , tức là chờ đợi 1 điều kiện nào đó thì luồng sở hữu điều kiện đó phải gọi phương thức notify() hoặc notifyall() để thông báo cho luồng chạy lại
+ Nếu luồng đang bị Block vì I/O thì quá trình I/O phải được hoàn tất

Như vậy, nếu luồng bị sleep 10 giây, mà nếu mới trải qua 5 giây, dù ta có gọi phương thức resume() thì luồng vẫn không thể chạy lại mà sẽ xảy ra ngoại lệ (Sẽ đề cập các ngoại lệ sâu hơn ở phần sau)

Dead:
Một thread bị rơi vào trạng thái Dead do 1 trong 2 cách: Chết tự nhiên hoặc bị ép dừng lại. Ví dụ dưới, phương thức run() bao gồm 1 vòng lặp hữu hạn, Nó sẽ lặp đi lặp lại 100 lần rồi thoát.

PHP Code:

       public void run() {
int i = 0;
while (i < 100) {
i++;
System.out.println("i = " + i);
}
}

Một thread sẽ chết 1 cách tự nhiên khi nó hoàn thành các lệnh trong phương thức run().
Bạn cũng có thể kết thúc 1 luồng bất kỳ lúc nào bằng cách gọi phương thức stop().
Đoạn mã sau đặt luồng vào trạng thái sleep 10s, sau khi luồng “thức dậy” sẽ bị stop()

PHP Code:

Thread myThread = new MyThreadClass();
myThread.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e){
}
myThread.stop();

Phương thức stop() ném ra 1 ngoại lệ ThreadDeath để “giết” luồng này. Vì vậy, các luồng này sẽ “chết” không đồng bộ. Các luồng sẽ “chết” khi nó nhận được 1 ngoại lệ ThreadDeath.

Phương thức stop() sẽ làm phương thức run() kết thúc đột ngột, đây không phải là 1 cách hay để kết thúc 1 luồng và bị SUN xem là nguy hiểm (Mình sẽ đề cập kỹ hơn ở phần Đồng bộ hóa sắp tới). Mình cũng sẽ đề xuất 1 phương pháp kết thúc luồng tốt hơn ở các phần sau.

2. Các trạng thái luồng do java định nghĩa
Ở phần trên, chúng ta đã đề cập chi tiết đến các trạng thái chung nhất của luồng và các nguyên nhận đưa luồng vào trạng thái đó cũng như cách đưa luồng thoát khỏi trạng thái đó. Ở phần này chúng ta sẽ đề cập đến cụ thể các trạng thái của luồng theo cách định nghĩa của java ở những trường hợp cụ thể bởi các nguyên nhận cụ thế.

Java định nghĩa các trạng thái của luồng trong các thuộc tính static trong Thread.State
Thread.State.NEW
Thread ở trạng thái "New Thread" như đã đề cập ở phần trên
Thread.State.RUNNABLE
Thread ở trạng thái "Runnable" như đã đề cập ở phần trên
Thread.State.BLOCKED
Đây là 1 dạng của trạng thái “Not Runnable”. Thread chờ 1 đối tượng bị lock bởi jmv Monitor (jmv Monitor sẽ để cập sâu ở chương "Đồng bộ hóa các luồng"),.
Thread.State.WAITING
Đây là 1 dạng của trạng thái “Not Runnable”. Thread đang chờ 1 notify() từ 1 thread khác. Thread rơi vào trạng thái này do phương thức wait() hoặc join()
Thread.State.TIMED_WAITING
Đây là 1 dạng của trạng thái “Not Runnable”. Thread đang chờ 1 notify() từ 1 thread khác trong 1 thời gian nhất định, Thread rơi vào trạng thái này do phương thức wait(long timeout) hoặc join(long timeout)
Thread.State.TERMINATED
Thread đã hoàn thành công việc trong run() hoặc bị stop()

* Các hàm wait,join,notify,jmv monitor và các trạng thái chi tiết này mình sẽ đề cập chi tiết ở phần “Đồng bộ hóa các luồng”

3. Phương thức suspend,resume,sleep,stop,destroy,isAlive,join,yei ld
Public void suspend();
Đây là phương thức làm tạm dừng hoạt động của 1 luồng nào đó bằng các ngưng cung cấp CPU cho luồng này. Để cung cấp lại CPU cho luồng ta sử dụng phương thức resume(). Cần lưu ý 1 điều là ta không thể dừng ngay hoạt động của luồng bằng phương thức này. Phương thức suspend() không dừng ngay tức thì hoạt động của luồng mà sau khi luồng này trả CPU về cho hệ điều hành thì không cấp CPU cho luồng nữa.

Ví dụ: Hệ điều hành phân phối CPU cho mỗi luồng 20ms cho mỗi lần cấp phát. Giả sử luồng này đã dùng tới 10ms thì bị suspend() thì luồng không lập tức trả CPU và dừng lại mà vẫn dùng tiếp 10ms còn lại cho đủ 20ms và sau đó luồng không được cấp CPU nữa

public void resume();
Đây là phương thức làm cho luồng chạy lại khi luồng bị dừng do phương thức suspend() bên trên. Phương thức này sẽ đưa luồng vào lại lịch điều phối CPU để luồng được cấp CPU chạy lại bình thường.

PHP Code:

Public void sleep(long milis);
Public void sleep(long milis,long nanos);
// Long milis: Là khoảng thời gian tính bằng mili giây mà luồng sẽ tạm ngưng hoạt động.
// Long nanos: Là khoảng thời gian tính bằng nano giây mà luồng sẽ tạm ngưng hoạt động.

Đây là phương thức làm luồng bị đưa vào trạng thái “ngủ”. Tức là luồng không thực hiện công việc của mình trong 1 khoảng thời gian nhất định do tham số milis và nanos quy định.
Như đã đề cập ở phần trước, cần phân biệt luồng bị dừng do suspend() và sleep(), luồng bị sleep() sẽ không thực hiện công việc của mình trong 1 khoảng thời gian nhất định, nhưng khoảng thời gian này luồng vẫn được cấp CPU, còn suspend() luồng không được cấp CPU để thực hiện công việc và thời gian tạm ngưng của luồng là không thể biết trước mà phải đợi đến khi phương thức resume() được gọi thì luồng mới được cấp CPU lại.

Public void stop();
Luồng này sẽ kết thúc phương thức run() bằng cách ném ra 1 ngoại lệ ThreadDeath, điều này cũng sẽ làm luồng kết thúc 1 cách ép buộc. Nếu giả sử, trước khi gọi stop() mà luồng đang nắm giữa 1 đối tượng nào đó hoặc 1 tài nguyên nào đó mà luồng khác đang chờ thì có thể dẫn tới việc sảy ra deadlock.

Public void isAlive();
Phương thức này kiểm tra xem luồng còn active hay không. Phương thức sẽ trả về true nếu luồng đã được start() và chưa rơi vào trạng thái dead. Nếu phương thức trả về false thì luồng đang ở trạng thái “New Thread” hoặc là đang ở trạng thái “Dead”

PHP Code:

Public void join();
Public void join(long milis);
Public void join(long milis,long nanos);
// Long milis: Là khoảng thời gian tính bằng mili giây mà luồng gọi phương thức này sẽ tạm ngưng hoạt động. để chờ luồng bị gọi.
// Long nanos: Là khoảng thời gian tính bằng nano giây mà luồng gọi phương thức này sẽ tạm ngưng hoạt động. để chờ luồng bị gọi.


Sau khi ta start() các luồng để các luồng này bắt đầu chạy, chắc chắn rằng, không có luồng nào biết luồng nào xong việc trước và luồng nào xong việc của luồng ấy thì nó sẽ tự kết thúc mà không chờ đợi điều gì cả. Tuy nhiên vì 1 lý do nào đó mà ta 1 luồng đợi 1 luồng khác làm xong việc rồi mới làm tiếp thì ta có thể sử dụng phương thức này. Đây có thể được coi là 1 sự kết hợp giữa phương thức sleep và isAlive().

Ví dụ: Sau khi t.start() luồng 1, luồng main có 1 câu lệnh in ra “Luồng t đã hoàn tất” thì chắc chắn rằng, luồng t chưa hoàn tất thì luồng main vẫn in ra câu này, bởi vì sau khi thực hiện câu lệnh t.start() luồng main sẽ thực hiện lệnh tiếp theo.
Tuy nhiên, nếu sau khi t.start() mà ta thêm 1 câu lệnh t.join() thì sau khi luồng t được khởi động, luồng main sẽ sleep hoàn toàn, không làm bất kỳ điều gì cho đến khi luồng t hoàn tất thì các câu lệnh sau t.join() lại tiếp tục được thực hiện. Do đó, ta nói join() là sự kết hợp giữa sleep và isAlive. Nếu có thêm tham số milis và nanos trong join() thì luồng đợi sẽ chỉ đợi tối đa tới thời gian này mà thôi

Để minh chứng cho điều này ta thử ví dụ sau:

PHP Code:

package MultiThread;

public class ThreadX extends Thread{

public ThreadX(String name)
{
super(name);
}
public void run()
{
System.out.println("Tên luồng:"+getName());
for(int i=0;i<5;i++)
{
try {
sleep(100); //--Sleep 100ms
} catch (InterruptedException e) {
//--Ko làm gì cả nếu có ngoại lệ
}
System.out.println("i="+i);
}
}
}  

PHP Code:

package MultiThread;
public class Main {
public static void main(String[] args) {ThreadX t=new ThreadX("Luồng X");
t.start();
System.out.println("t đã xong");
}
}

Kết quả:

Kết quả không dùng join, dòng “t đã xong” in ra ngay sau khi start()

Ví dụ tiếp theo có dùng join (Sử dụng lại lớp ThreadX ở trên):

PHP Code:

package MultiThread;
public class Main {
public static void main(String[] args) {ThreadX t=new ThreadX("Luồng X");
t.start();
try {
t.join(); //---Đợi t làm xong rồi mới làm tiếp
} catch (InterruptedException e) {}
System.out.println("t đã xong");
}
}

Kết quả:

Kết quả có dùng join(), luồng chính đợi luồng t làm xong rồi mới thực hiện các lệnh tiếp theo 

Public void yeild();
Như ta đã biết, HĐH đa nhiệm sẽ phân phối CPU cho các tiến trình, các luồng theo vòng xoay. Mỗi luồng sẽ được cấp CPU trong 1 khoảng thời gian nhất định, sau đó trả lại CPU cho HĐH, HĐH sẽ cấp CPU cho luồng khác. Các luồng sẽ nằm chờ trong hàng đợi Ready để nhận CPU theo thứ tự.

Java có cung cấp cho chúng ta 1 phương thức khá đặc biệt là yeild(), khi gọi phương thức này luồng sẽ bị ngừng cấp CPU và nhường cho luồng tiếp theo trong hàng chờ Ready. Lưu ý 1 điều rằng, luồng không phải ngưng cấp CPU như suspend mà chỉ ngưng cấp trong lần nhận CPU đó mà thôi.

VD: CPU sẽ phân phối CPU cho mỗi luồng 20ms rồi thu lại cấp cho luồng khác. Luồng l1 đang nắm giữ CPU và đã sử dụng CPU được 10ms rồi, khi gọi phương thức yeild() luồng l1 sẽ ngưng giữ CPU mà trả lại CPU cho HĐH rồi quay lại hàng chờ Ready cho lần cấp phát tiếp theo.

Post a Comment

أحدث أقدم