Thứ Năm, 6 tháng 6, 2013

Đa luồng trong Java (Multithreading in Java) - Phần 1: Cách tạo luồng và ThreadID, ThreadName ,Piority, StackSize


Đa luồng trong java (Multithreading in java) - Phần 1
Các nội dung trong phần này:

  • Luồng trong Java

  • Cách tạo và quản lý luồng trong Java

  • Tạo luồng bằng cách thừa kế từ lớp Thread

  • Cách tạo luồng bằng cách hiện thực từ Interface Runnable

  • Một số thông tin liên quan đến luồng (ThreadID, ThreadName - Tên luồng ,Piority - độ ưu tiên,StackSize -Độ lớn ngăn xếp của luồn)



1. Luồng trong java là gì ?
Luồng là đơn vị nhỏ nhất trong java có thể thực hiện được 1 công việc riêng biệt. Các luồng được quản lý bởi máy ảo java. Một ứng dụng java ngoài luồng chính có thể có các luồng khác thực thi đồng thời. Đa luồng giúp cho các tác vụ được xử lý độc lập giúp công việc được hoàn thành nhanh chóng. Trình duyệt web hay các chương trình chơi nhạc là 1 ví dụ điển hình về đa luồng.
+ Khi duyệt 1 trang web, có rất nhiều hình ảnh, CSS, javascript... được tải đồng thời bởi các luồng khác nhau
+ Khi play nhạc, chúng ta vẫn có thể tương tác được với nút điều khiển như: Play, pause, next, back ... vì luông phát nhạc là luồng riêng biệt với luông tiếp nhận tương tác của người dùng

2. Cách Tạo và quản lý luồng trong java
Trong java ta có thể tạo ra 1 luồng dễ dàng bằng cách tạo 1 đối tượng của lớp được thừa kế từ lớp Thread hoặc implements từ giao diện Runnable.

Khi nào implements từ interface Runnable ?
+ Khi bạn muốn kế thừa từ 1 lớp khác ngoài lớp thread, nếu bạn kế thừa nữa từ lớp Thread thì sẽ không được vì java không hỗ trợ đa thừa kế do đó ta phải implements từ interface Runnable.
+ Trong trường hợp còn lại ta có thể kế thừa từ lớp Thread

2.1 Tạo luồng bằng cách kế thừa từ lớp Thread
Để tạo luồng bằng cách tạo lớp kế thừa từ lớp Thread, ta phải làm các công việc sau :
+ Khai báo 1 lớp mới kế thừa từ lớp Thread
+ Override lại phương thức run ở lớp này, những gì trong phương thức run sẽ được thực thi khi luồng bắt đầu chạy. Sau khi luồng chạy xong tất cả các câu lệnh trong phương thức run thì luồng cũng tự hủy.
+ Tạo 1 thể hiện (hay 1 đối tượng) của lớp ta vừa khai báo. 
+ Sau đó gọi phương thức start() của đối tượng này để bắt đầu thực thi luồng.

Lưu ý : Tuy ta khai báo những công việc cần làm của luồng trong phương thức run() nhưng khi thực thi luồng ta phải gọi phương thức start(). Vì đây là phường thức đặc biệt mà java xây dựng sẵn trong lớp Thread, phương thức này sẽ cấp phát tài nguyên cho luồng mới rồi chạy phương thức run() ở luồng này. Vì vậy, nếu ta gọi phương thức run() mà không gọi start() thì cũng như ta gọi 1 phương thức của 1 đối tượng bình thường và phương thức vẫn chạy trên luồng mà gọi phương thức chứ không chạy ở luồng mới tạo ra nên vẫn chỉ có 1 luồng chính làm việc chứ ứng dụng vẫn không phải là đa luồng.

Ví dụ cách tạo luồng bằng cách kế thừa từ lớp Thread :

+ Khai báo lớp mới ThreadX kế thừa từ lớp Thread và override phương thức run() : 

PHP Code:



package MultiThread;

public class ThreadX extends Thread{
private String prefix="";
public ThreadX(String p)
{
prefix=p;
}
public void run()
{
for(int i=0;i<100;i++)
System.out.println(prefix+i);
}
}



+ Tạo các đối tượng thuộc lớp ThreadX và start() chúng :

PHP Code:



package MultiThread;

public class Main {

public static void main(String[] args) {
ThreadX t1=new ThreadX("Luồng thứ 1:");
ThreadX t2=new ThreadX("Luồng thứ 2:");
ThreadX t3=new ThreadX("Luồng thứ 3:");
t1.start();
t2.start();
t3.start();
}

}



+ Kết quả ta nhận được như sau :

Ví dụ đa luồng bằng cách kế thừa từ lớp Thread

2.1 Cách tạo luồng bằng cách hiện thực từ Interface Runnable
Để tạo luồng bằng cách hiện thực từ Interface Runnable, ta phải làm các công việc sau :
+ Khai báo 1 lớp mới implements từ Interface Runnable
+ Hiện thực phương thức run() ở lớp này, những gì trong phương thức run() sẽ được thực thi khi luồng bắt đầu chạy. Sau khi luồng chạy xong tất cả các câu lệnh trong phương thức run thì luồng cũng tự hủy.
+ Tạo 1 thể hiện (hay 1 đối tượng) của lớp ta vừa khai báo. (VD : Tên đối tượng là r1)
+ Tạo 1 thể hiện của lớp Thread bằng phương thức khởi tạo : 
Thread(Runnable target)
Runnable target: Là 1 đối tượng thuốc lớp được implements từ giao diện Runnable
VD:
Thread t1=new Thread(r1);
+ Gọi phương thức start() của đối tượng t1

Ví dụ cách tạo luồng bằng cách hiện thức giao diện Runnable :

+ Khai báo lớp mới RunX implements từ interface Runnable và hiện thực phương thức run() :

PHP Code:

package MultiThread;
public class RunX implements Runnable{
private String prefix="";
public RunX(String p)
{
prefix=p;
}
public void run()
{
for(int i=0;i<100;i++)
System.out.println(prefix+i);
}
}  


+ Tạo các thể hiện của lớp RunX,Thread và start() chúng :

PHP Code:



package MultiThread;

public class Main {

public static void main(String[] args) {
RunX r1=new RunX("Luồng thứ 1:");
RunX r2=new RunX("Luồng thứ 2:");
RunX r3=new RunX("Luồng thứ 3:");
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
Thread t3=new Thread(r3);
t1.start();
t2.start();
t3.start();
}

}



+ Kết quả ta nhận được như sau :

Ví dụ đa luồng bằng cách hiện thực từ Interface Runnable

3. Một số thông tin liên quan đến luồng (ThreadID,ThreadName,Piority,StackSize)
Luồng được thành lập bởi 1 định danh (ThreadID), Bộ đếm chương trình (Counter), Tập Thanh Ghi (Register) và Ngăn xếp (Stack). Các luồng sẽ có 1 độ ưu tiên (Priority) nhất định. Trong phần này chúng ta sẽ đề cập chi tiết đến các thông tin này bao gồm:
+ ThreadID
+ ThreadName
+ Priority
+ StackSize

3.1 ThreadID
ThreadID là định danh của luồng, nó dùng để phân biệt với các luồng khác cùng tiến trình hoặc cùng tập luồng. Đây là thông số mà máy ảo java tự tạo ra khi ta tạo luồng nên ta không thể sửa đổi cũng như áp đặt thông số này khi tạo luồng. Nhưng ta có thể lấy được nó thông qua phương thức getId() của lớp Thread

VD (Sử dụng lại lớp ThreadX khai báo ở trên ):

PHP Code:



public class Main {

public static void main(String[] args) {
ThreadX t1=new ThreadX("Luồng thứ 1:");
ThreadX t2=new ThreadX("Luồng thứ 2:");
ThreadX t3=new ThreadX("Luồng thứ 3:");
System.out.println("ID luồng 1:"+t1.getId());
System.out.println("ID luồng 2:"+t2.getId());
System.out.println("ID luồng 3:"+t3.getId());
t1.start();
t2.start();
t3.start();
}

}



Kết quả:

Kết quả ví dụ dùng phương thức getId()

3.2 ThreadName
ThreadName là tên của luồng, đây là thuộc tính mà ta có thể đặt hoặc không đặt cho luồng. Nếu ta không đặt cho luồng thì máy ảo java sẽ tự đặt với quy tắc sau: “Thread-” + Thứ tự luồng được tạo ra, bắt đầu từ 0.

VD:

PHP Code:



package MultiThread;

public class Main {

public static void main(String[] args) {
ThreadX t1=new ThreadX("Luồng thứ 1:");
ThreadX t2=new ThreadX("Luồng thứ 2:");
ThreadX t3=new ThreadX("Luồng thứ 3:");
System.out.println("Tên luồng 1:"+t1.getName());
System.out.println("Tên luồng 2:"+t2.getName());
System.out.println("Tên luồng 3:"+t3.getName());
}
}



Kết quả:

Kết quả ví dụ Tên luồng jmv tự đặt

Nếu muốn đặt tên cho luồng, ta có thể đặt tên thông qua phương thức khởi tạo:
1.Thread(String name)
2.Thread(ThreadGroup group, Runnable target)
3.Thread(ThreadGroup group, Runnable target, String name, long stackSize)
4.Thread(ThreadGroup group, String name)

hoặc phương thức :
5.void setName(String name)

Và ta có thể lấy tên luồng thông qua phương thức
Public String getName()

Ví dụ về tên luồng:

PHP Code:



package MultiThread;

public class ThreadX extends Thread{
public ThreadX(String name)
{
super(name);
}
public ThreadX()
{
super();
}
public void run()
{
for(int i=0;i<100;i++)
System.out.println("Tên luồng:"+getName()+",i="+i);
}
}




PHP Code:

package MultiThread;
public class Main {
public static void main(String[] args) {
ThreadX t1=new ThreadX("Luồng thứ 1");
ThreadX t2=new ThreadX("Luồng thứ 2");
ThreadX t3=new ThreadX();
t3.setName("Luồng thứ 3");
t1.start();
t2.start();
t3.start();
}
}


Kết quả:

Ví dụ về tên luồng tự đặt

3.3 Độ ưu tiên của luồng (Priority)
Như đã nói ở phần trước, mỗi luồng có 1 độ ưu tiên nhất định. Đây sẽ là thông số quyết định mức ưu tiên khi cấp phát CPU cho các luồng. Trong java, đế đặt độ ưu tiên cho 1 luồng ta dùng phương thức
void setPriority(int newPriority)
int newPriority : Là giá trị từ 1 đến 10. 
Java có định nghĩa sẵn 3 mức ưu tiên chuẩn như sau:
Thread.MAX_PRIORITY (giá trị 10)
Thread.NORM_PRIORITY (giá trị 05)
Thread.MIN_PRIORITY (giá trị 01)

Để lấy độ ưu tiên của 1 luồng, ta dùng phương thức 
int getPriority()

Ví dụ về độ ưu tiên :

PHP Code:

package MultiThread;
public class ThreadX extends Thread{
public ThreadX(String name)
{
super(name);
}
public void run()
{
for(int i=0;i<100;i++)
System.out.println("Tên luồng:"+getName()+", Độ ưu tiên:"+getPriority()+",i="+i);
}
}}
}



PHP Code:



package MultiThread;

public class Main {

public static void main(String[] args) {
ThreadX t1=new ThreadX("Luồng thứ 1");
ThreadX t2=new ThreadX("Luồng thứ 2");
ThreadX t3=new ThreadX("Luồng thứ 3");
t1.setPriority(1);
t2.setPriority(5);
t3.setPriority(10);
t1.start();
t2.start();
t3.start();
}
}



Kết quả:

Kết quả ví dụ độ ưu tiên, luồng 3 độ ưu tiên lớn nên được cấp CPU tần suất cao.

3.4 Độ lớn ngăn xếp của luồng (StackSize)
StackSize là độ lớn của ngăn xếp (tính bằng byte) mà luồng có thể dùng trong quá trình thực thi. Nếu ta không quy định giá trị này hoặc đặt giá trị này bằng 0 thì nó sẽ phụ thuộc vào giá trị mặc định mà JMV quy định và JMV sẽ tự điều chỉnh cho hợp lý. Ta có thể tùy chỉnh các thông số mặc định này bằng phần mềm ArcGIS Java Configuration Tool (Hình dưới). Còn nếu ta có quy định, thread sẽ chỉ cho phép dùng Stack tới mức tối đa mà giá trị này quy định.

 

Giao diện ArcGIS Java configuration tool

Tuy nhiên, theo thông tin tại : http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html của SUN thì :
+ Thông số stackSize này còn phụ thuộc vào nền tảng mà ta đang phát triển ứng dụng. (VD: Windows NT là 1 nên tảng). Trên 1 số nền tảng, nếu ta quy định tham số này lớn thì có thể giải quyết được vấn đề StackoverFlow(lỗi tràn stack). 

+ Một điều cần lưu ý là tham số này quy định độ lớn stack bằng byte chứ không phải số phần tử có thể đưa vào stack. Do đó chiều sâu của stack có thể đệ quy(hay số phần tử có thể có trong stack) cũng hoàn toàn phụ thuộc vào nền tảng, và trên 1 số nền tảng thì tham số này không có tác dụng gì.

Để quy định thuộc tính stackSize này, ta có thể truyền vào trong phương thức khởi tạo luồng:
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

Trong phươc thức trên, có tham số ThreadGroup group ta sẽ đề cập chi tiết ở phần sau

0 nhận xét:

Đăng nhận xét