Đa luồng trong java (multithreading in Java)
Trang 1 trong tổng số 1 trang
Đa luồng trong java (multithreading in Java)
Multithread trong java
1.1 Khái niệm
Thread là một đơn vị (nhóm) chứa các dòng điều khiển trong một tiến trình hay ứng dụng, chúng được hệ điều hành sử dụng để thực thi một công việc. Và với cơ chế multithread ứng dụng của ta có thể thực thi nhiều công việc đồng thời. Mỗi thread sẽ có một chỉ số xác định quyền ưu tiên, thread nào có quyền ưu tiên cao sẽ được thực thi trước, và thấp thì thực thi sau. Việc thực thi và đánh chỉ số ưu tiên sẽ được thực hiện tự động bởi hệ điều hành để tránh một thread nào đợi quá lâu mà không được vào xử lý.
Cũng như các ngôn ngữ lập trình khác, java hỗ trợ ngay (trong bản thân ngôn ngữ) khá đầy đủ các yêu cầu, xử lý của một ứng dụng multithread.
1.2 Tạo và quản lý thread
Trong chương trình java luôn có một thread chính được thực thi, các thread khác sẽ được tạo ra như các thread con, việc mở đầu và kết thúc một ứng dụng được thực hiện bởi thread chính đó.
Hình dưới minh họa vòng đời của một thread:
Để tạo và quản lý thread trong java, chúng ta dựa vào lớp Thread và Runnable interface
Runnable là một interface dùng để các lớp khác implement để sử dụng thread. Trong lớp implement đó bắt buộc phải định nghĩa một phương thức là void run().
Ví dụ ta có 2 lớp StringThread implement Runnable, và một lớp TestClass để tạo ra thread và run nó.
public class StringThread implements Runnable {
private String str;
private int num;
StringThread(String s, int n)
{
str = new String(s);
num = n;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
for (int i = 1; i <= num; i++)
System.out.print(str + " ");
}
}
public class TestClass {
/**
* @param args
*/
public static void main(String[] args) {
StringThread t = new StringThread("Thread", 10);
new Thread(t).start();
}
}
Kết quả in ra màn hình sẽ là: Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread
Thread là một lớp viết sẵn trong java, nó implement Runnable interface. Nếu chúng ta muốn sử dụng thread chỉ cần viết một lớp và kế thừa (extends) từ lớp Thread này.
Một số hàm thông dụng của Thread
• start bắt đầu thực thi thread
• sleep đưa trạng thái về trạng thái đợi (không thực thi nữa)
• resume thực thi lại thread từ trạng thái đợi hoặc dead-lock
• join đợi để cho đến khi thread này chết
• isAlive kiểm tra thread còn đang sống hay đã bị kill
• getName lấy tên của thread
• getPriority trả về độ ưu tiên của thread
• setName đặt tên cho thread
• setPriority đặt độ ưu tiên cho thread
• …
Ví dụ tương tự việc sử dụng thread với Runnable interface, chúng ta cũng có thể kế thừa tự lớp có sẵn là Thread như sau:
class StringThread extends Thread {
public void run() {
System.out.println("Inherit successfully from Thread class!");
}
}
public class TestClass {
public static void main(String[] args) {
StringThread t = new StringThread();
new Thread(t).start();
}
}
Kết quả in ra sẽ là: Inherit successfully from Thread class!
1.3 Các lớp Timer, TimerTask và SchedulingTask
Timer là một lớp tiện ích giúp cho việc lập lịch và kiểm soát việc thực thi một task vụ nào đó. Ví dụ bạn muốn dùng việc cập nhật thông trong 5 phút tới, hay thông tin chỉ được tồn tại 10h sau đó sẽ tự động bị xóa đi,… Một số hàm thông dụng trong Timer như:
• schedule lên lịch để thực thi task khi nào bắt đầu, kết thúc hay lặp lại, …
• cancel dừng timer và hủy tất cả các task đã lên lịch trong timer
• purge xóa tất cả các task đã dùng trong hàng đợi timer
TimerTask là một lớp trừu tượng implement Runnable interface, nó giúp cho việc lập lịch thực thi các thread
Ví dụ ta có một lớp MyTimerTask kế thừa từ TimerTask như bên dưới
class MyTimerTask extends TimerTask {
public void run() {
System.out.println("Running the scheduled task by Timer");
System.gc();
}
}
public class TestClass {
public static void main(String[] args) {
Timer timer = new Timer();
MyTimerTask task = new MyTimerTask();
timer.schedule(task, 3000, 2000);//Execute task after 3 senconds and then repeat in 2 seconds period
}
}
Kết quả in ra màn hình sau 5 giây sẽ là: Running the scheduled task by Timer
Running the scheduled task by Timer
1.4 Xử lý các vấn đề chúng trong multithread như synchronized, lock, dead lock, wait, notify …
Đồng bộ (Synchronization) là cơ chế giúp cho đồng bộ việc truy cập cùng tài nguyên của nhiều thread khác nhau. Như chúng ta biết vì cơ chế thread là cơ chế làm việc đồng thời do đó có khả năng một tài nguyên sẽ bị truy cập bởi nhiều thread khác nhau.
Ví dụ ta xây dựng 3 lớp Socola, Producer, Consumer. Lớp Socola chứa thông tin, lớp Producer tạo thông tin và gán vào cho Socola, Lớp Consumer sẽ ăn (tiêu thụ) thông tin đó.
public class TestClass {
public class Socola {
private int contents = 0;
public boolean isRemainingContents;
public synchronized int get() {
//print onto screen
System.out.println("\tConsumer get " + this.contents);
return contents;
}
public synchronized void set(int piContents) {
this.contents = piContents;
//print onto screen
System.out.println("Producer set " + this.contents);
}
}
public class Producer extends Thread {
private Socola socola;
Producer(Socola pSocola) {
socola = pSocola;
}
public void run() {
for(int i = 0; i < 10; i++) {
socola.set(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public class Consumer extends Thread {
private Socola socola;
Consumer(Socola pSocola) {
socola = pSocola;
}
public void run() {
for(int i = 0; i < 10; i++) {
socola.get();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
TestClass t = new TestClass();
Socola s = t.new Socola();
Producer p = t.new Producer(s);
Consumer c = t.new Consumer(s);
p.start();
c.start();
}
}
Kết quả in ra màn hinh sẽ không giống nhau tại các lần chạy, ví dụ 1 lần chạy:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Consumer get 4
Producer set 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Từ ví dụ trên ta thấy có chung một tài nguyên là Socola mà có 2 đối tượng cùng truy cập có thể sinh ra mâu thuẫn khi truy cập đồng thời. Bây giờ ta muốn là chỉ khi Producer tạo ra thông tin thì Consumer mới ăn thông tin đó, không muốn Consumer ăn nhiều lần cùng một thông tin, khi Producer đang tạo ra thông tin thì Consumer đứng chờ, khi nào Producer tạo ra thông tin xong thì Consumer mới vào ăn thông tin. vậy thì ta phải dùng cơ chế đồng bộ trong java với các từ khóa synchronized, wait(), notify(), notifyAll() và chỉnh lại 2 hàm get và set trong lớp Socola như sau:
public synchronized int get() {
while(isRemainingContents == false) {
try {
wait();
} catch (InterruptedException e) {
}
}
isRemainingContents = false;
//print onto screen
System.out.println("\tConsumer get " + this.contents);
//Notify Producer to produce new contents
notifyAll();
return contents;
}
public synchronized void set(int piContents) {
while(isRemainingContents == true) {
try {
wait();
} catch (InterruptedException e) {
}
}
this.contents = piContents;
isRemainingContents = true;
//print onto screen
System.out.println("Producer set " + this.contents);
//Notify Consumer to eat this contents
notifyAll();
}
}
Kết quả in ra luôn luôn như sau:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Producer set 5
Consumer get 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Consumer get 9
Có 3 hàm cần để ý:
• wait() đưa thread vào trạng thái chờ khi nào được notify() hoặc notifyAll() thì sẽ thực hiện tiếp
• notify() đánh thức thread đang đợi, nếu có nhiều thread đang đợi, thì một trong những thread đó được đánh thức thôi.
• notifyAll() đánh thức tất cả các thread đang đợi
Note: Nhiều khi chúng ta không cần hoặc không thể đồng bộ nguyên hàm (dùng synchronized) mà chỉ cần đồng bộ một vài dòng code truy cập tài nguyên, ta có thể dùng khối đồng bộ như sau:
synchronized(object) {
// Các cấu lệnh cần đồng bộ
}
Ví dụ: Khi bạn sử dụng lớp Socola là của khách hàng bạn đưa cho, bạn không thể chỉnh sửa định nghĩa hàm get và set trong Socola được vậy thì bạn có thể dùng khối đồng bộ bên trên cũng sẽ cho kết quả tương tự.
Deadlock (khóa chết) trong xử lý đa luồng xảy là khi mà một thread được đưa vào trạng thái chờ tài nguyên và không bao giờ được đánh thức lại vì tại nguyên đó không bao giờ được giải phóng. Để rõ hơn ta ví dụ A, B, C là thread trong trạng thái chờ; rA, rB, rC là tài nguyên dùng chung và chỉ duy nhất một thread sử dụng trong một thời điểm, tài nguyên chỉ được giải phóng khi thread làm xong và không sử dụng nữa.
• A giữ rA, chờ rB
• B giữ rB, chờ rC
• C giữ rC, chờ rA
Từ ví dụ trên ta thấy A, B, C là những thread đang ở trong trạng thái chờ tài nguyên, tuy nhiên tài nguyên của chúng không bao giờ được giải phóng, và mãi mãi chúng cứ chờ thôi. Để hạn chế deadlock thì chúng ta phải đưa ra, cải tiến việc cấp phát, thu hồi tài nguyên cho các thread và hiện này có khá nhiều thuật toán cải tiến việc này, tuy nhiên không có cái nào giúp tránh hoàn toàn deadlock.
1.5 Những lớp tiện ích trong lập trình đa luồng (multithread)
Trong java đã xây dựng một vài lớp giúp hỗ trợ lập trình đa luồng, chúng được đặt trong gói Concurent như: Semaphore, Lock, Condition, ConcurrentMap, ConcurrentLinkedQueue, …
Semaphore là một lớp tiện ích đã xây dựng trong java (java 5) dùng để giới hạn số lượng thread truy cập đến tài nguyên. Đầu tiên thread cần yêu cầu sự chấp nhận truy cập tài nguyên từ semaphore bằng hàm acquire, và thread sẽ về trạng thái đợi (block) cho đến khi được chấp nhận, khi thread được chấp nhận nó thực hiện công việc của nó xong sẽ gọi hàm release để trả về cho semaphore một sự chấp nhận còn trống để semaphore cấp cho thread khác.
Lock là một interface được xây dựng nhằm mở rộng hơn việc quản lý truy cập tài nguyên trong nhiều thread hơn so với cách dùng synchronized chẳng hạn như có thể hỗ trợ nhiều điều kiện (condition object) trong lock, hoặc hỗ trợ nhiều thuật toán cho chain-lock để tránh deadlock tốt hơn, … Khi dùng Lock chúng ta nên theo một template chuẩn như sau:
Lock l = ...;
l.lock();
try {
// Các task truy cập tài nguyên bảo vệ bởi lock đặt ở đây
} finally {
l.unlock();
}
Condition là một interface dùng để nhóm cách xử lý trong các phương thức (wait, notify, notifyAll) của lớp Object vào thành một đối tượng nhằm đưa ra nhiều chọn lựa hơn trong việc đồng bộ, ví dụ chúng ta có thể dùng nhiều condition và khi nào chúng ta muốn đánh thức chính xác điều kiện nào được sử dụng tài nguyên.
Ví dụ ta dùng lại ví dụ Consumer-Producer ở trên, thêm vào 2 biến lock, condition và chỉnh sửa hai làm get và put thế là ta cũng được kết quả như dùng với từ khóa synchronized:
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
public int get() {
int result;
lock.lock();
try {
while(isRemainingContents == false) {
try {
condition.await();
} catch (InterruptedException e) {
}
}
isRemainingContents = false;
//print onto screen
System.out.println("\tConsumer get " + this.contents);
//get value
result = contents;
//Notify Producer to produce new contents
condition.signalAll();
} finally {
lock.unlock();
}
return result;
}
public void set(int piContents) {
lock.lock();
try {
while(isRemainingContents == true) {
try {
condition.await();
} catch (InterruptedException e) {
}
}
this.contents = piContents;
isRemainingContents = true;
//print onto screen
System.out.println("Producer set " + this.contents);
//Notify Consumer to eat this contents
condition.signalAll();
} finally {
lock.unlock();
}
}
Kết quả luôn luôn là thế này:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Producer set 5
Consumer get 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Consumer get 9
ConcurrentMap là một interface kế thừa từ Map dùng để cải tiến, tối ưu hơn khi chạy Map trong ứng dụng đa luồng (multithread)
Ví dụ cách dùng ConcurrentHashMap tương tự dùng Map, tuy nhiên người ta đã thống kê là nếu chương trình có càng nhiều thread truy cập vào cMap này thì dùng ConcurrentMap sẽ cho performance tốt hơn rất nhiều so với dùng Map với đồng bộ kiểu synchronized
public static ConcurrentMap<String,String> cMap =
new ConcurrentHashMap<String, String>(1000);
public static void main(String[] args) {
cMap.putIfAbsent("id", "1");
cMap.putIfAbsent("name", "kien");
cMap.putIfAbsent("email", "nakien2a@yahoo.com");
System.out.println(cMap); //Print to screen
}
Kết quả: {name=kien, email=nakien2a@yahoo.com, id=1}
1.1 Khái niệm
Thread là một đơn vị (nhóm) chứa các dòng điều khiển trong một tiến trình hay ứng dụng, chúng được hệ điều hành sử dụng để thực thi một công việc. Và với cơ chế multithread ứng dụng của ta có thể thực thi nhiều công việc đồng thời. Mỗi thread sẽ có một chỉ số xác định quyền ưu tiên, thread nào có quyền ưu tiên cao sẽ được thực thi trước, và thấp thì thực thi sau. Việc thực thi và đánh chỉ số ưu tiên sẽ được thực hiện tự động bởi hệ điều hành để tránh một thread nào đợi quá lâu mà không được vào xử lý.
Cũng như các ngôn ngữ lập trình khác, java hỗ trợ ngay (trong bản thân ngôn ngữ) khá đầy đủ các yêu cầu, xử lý của một ứng dụng multithread.
1.2 Tạo và quản lý thread
Trong chương trình java luôn có một thread chính được thực thi, các thread khác sẽ được tạo ra như các thread con, việc mở đầu và kết thúc một ứng dụng được thực hiện bởi thread chính đó.
Hình dưới minh họa vòng đời của một thread:
Để tạo và quản lý thread trong java, chúng ta dựa vào lớp Thread và Runnable interface
Runnable là một interface dùng để các lớp khác implement để sử dụng thread. Trong lớp implement đó bắt buộc phải định nghĩa một phương thức là void run().
Ví dụ ta có 2 lớp StringThread implement Runnable, và một lớp TestClass để tạo ra thread và run nó.
public class StringThread implements Runnable {
private String str;
private int num;
StringThread(String s, int n)
{
str = new String(s);
num = n;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
for (int i = 1; i <= num; i++)
System.out.print(str + " ");
}
}
public class TestClass {
/**
* @param args
*/
public static void main(String[] args) {
StringThread t = new StringThread("Thread", 10);
new Thread(t).start();
}
}
Kết quả in ra màn hình sẽ là: Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread
Thread là một lớp viết sẵn trong java, nó implement Runnable interface. Nếu chúng ta muốn sử dụng thread chỉ cần viết một lớp và kế thừa (extends) từ lớp Thread này.
Một số hàm thông dụng của Thread
• start bắt đầu thực thi thread
• sleep đưa trạng thái về trạng thái đợi (không thực thi nữa)
• resume thực thi lại thread từ trạng thái đợi hoặc dead-lock
• join đợi để cho đến khi thread này chết
• isAlive kiểm tra thread còn đang sống hay đã bị kill
• getName lấy tên của thread
• getPriority trả về độ ưu tiên của thread
• setName đặt tên cho thread
• setPriority đặt độ ưu tiên cho thread
• …
Ví dụ tương tự việc sử dụng thread với Runnable interface, chúng ta cũng có thể kế thừa tự lớp có sẵn là Thread như sau:
class StringThread extends Thread {
public void run() {
System.out.println("Inherit successfully from Thread class!");
}
}
public class TestClass {
public static void main(String[] args) {
StringThread t = new StringThread();
new Thread(t).start();
}
}
Kết quả in ra sẽ là: Inherit successfully from Thread class!
1.3 Các lớp Timer, TimerTask và SchedulingTask
Timer là một lớp tiện ích giúp cho việc lập lịch và kiểm soát việc thực thi một task vụ nào đó. Ví dụ bạn muốn dùng việc cập nhật thông trong 5 phút tới, hay thông tin chỉ được tồn tại 10h sau đó sẽ tự động bị xóa đi,… Một số hàm thông dụng trong Timer như:
• schedule lên lịch để thực thi task khi nào bắt đầu, kết thúc hay lặp lại, …
• cancel dừng timer và hủy tất cả các task đã lên lịch trong timer
• purge xóa tất cả các task đã dùng trong hàng đợi timer
TimerTask là một lớp trừu tượng implement Runnable interface, nó giúp cho việc lập lịch thực thi các thread
Ví dụ ta có một lớp MyTimerTask kế thừa từ TimerTask như bên dưới
class MyTimerTask extends TimerTask {
public void run() {
System.out.println("Running the scheduled task by Timer");
System.gc();
}
}
public class TestClass {
public static void main(String[] args) {
Timer timer = new Timer();
MyTimerTask task = new MyTimerTask();
timer.schedule(task, 3000, 2000);//Execute task after 3 senconds and then repeat in 2 seconds period
}
}
Kết quả in ra màn hình sau 5 giây sẽ là: Running the scheduled task by Timer
Running the scheduled task by Timer
1.4 Xử lý các vấn đề chúng trong multithread như synchronized, lock, dead lock, wait, notify …
Đồng bộ (Synchronization) là cơ chế giúp cho đồng bộ việc truy cập cùng tài nguyên của nhiều thread khác nhau. Như chúng ta biết vì cơ chế thread là cơ chế làm việc đồng thời do đó có khả năng một tài nguyên sẽ bị truy cập bởi nhiều thread khác nhau.
Ví dụ ta xây dựng 3 lớp Socola, Producer, Consumer. Lớp Socola chứa thông tin, lớp Producer tạo thông tin và gán vào cho Socola, Lớp Consumer sẽ ăn (tiêu thụ) thông tin đó.
public class TestClass {
public class Socola {
private int contents = 0;
public boolean isRemainingContents;
public synchronized int get() {
//print onto screen
System.out.println("\tConsumer get " + this.contents);
return contents;
}
public synchronized void set(int piContents) {
this.contents = piContents;
//print onto screen
System.out.println("Producer set " + this.contents);
}
}
public class Producer extends Thread {
private Socola socola;
Producer(Socola pSocola) {
socola = pSocola;
}
public void run() {
for(int i = 0; i < 10; i++) {
socola.set(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public class Consumer extends Thread {
private Socola socola;
Consumer(Socola pSocola) {
socola = pSocola;
}
public void run() {
for(int i = 0; i < 10; i++) {
socola.get();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
TestClass t = new TestClass();
Socola s = t.new Socola();
Producer p = t.new Producer(s);
Consumer c = t.new Consumer(s);
p.start();
c.start();
}
}
Kết quả in ra màn hinh sẽ không giống nhau tại các lần chạy, ví dụ 1 lần chạy:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Consumer get 4
Producer set 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Từ ví dụ trên ta thấy có chung một tài nguyên là Socola mà có 2 đối tượng cùng truy cập có thể sinh ra mâu thuẫn khi truy cập đồng thời. Bây giờ ta muốn là chỉ khi Producer tạo ra thông tin thì Consumer mới ăn thông tin đó, không muốn Consumer ăn nhiều lần cùng một thông tin, khi Producer đang tạo ra thông tin thì Consumer đứng chờ, khi nào Producer tạo ra thông tin xong thì Consumer mới vào ăn thông tin. vậy thì ta phải dùng cơ chế đồng bộ trong java với các từ khóa synchronized, wait(), notify(), notifyAll() và chỉnh lại 2 hàm get và set trong lớp Socola như sau:
public synchronized int get() {
while(isRemainingContents == false) {
try {
wait();
} catch (InterruptedException e) {
}
}
isRemainingContents = false;
//print onto screen
System.out.println("\tConsumer get " + this.contents);
//Notify Producer to produce new contents
notifyAll();
return contents;
}
public synchronized void set(int piContents) {
while(isRemainingContents == true) {
try {
wait();
} catch (InterruptedException e) {
}
}
this.contents = piContents;
isRemainingContents = true;
//print onto screen
System.out.println("Producer set " + this.contents);
//Notify Consumer to eat this contents
notifyAll();
}
}
Kết quả in ra luôn luôn như sau:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Producer set 5
Consumer get 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Consumer get 9
Có 3 hàm cần để ý:
• wait() đưa thread vào trạng thái chờ khi nào được notify() hoặc notifyAll() thì sẽ thực hiện tiếp
• notify() đánh thức thread đang đợi, nếu có nhiều thread đang đợi, thì một trong những thread đó được đánh thức thôi.
• notifyAll() đánh thức tất cả các thread đang đợi
Note: Nhiều khi chúng ta không cần hoặc không thể đồng bộ nguyên hàm (dùng synchronized) mà chỉ cần đồng bộ một vài dòng code truy cập tài nguyên, ta có thể dùng khối đồng bộ như sau:
synchronized(object) {
// Các cấu lệnh cần đồng bộ
}
Ví dụ: Khi bạn sử dụng lớp Socola là của khách hàng bạn đưa cho, bạn không thể chỉnh sửa định nghĩa hàm get và set trong Socola được vậy thì bạn có thể dùng khối đồng bộ bên trên cũng sẽ cho kết quả tương tự.
Deadlock (khóa chết) trong xử lý đa luồng xảy là khi mà một thread được đưa vào trạng thái chờ tài nguyên và không bao giờ được đánh thức lại vì tại nguyên đó không bao giờ được giải phóng. Để rõ hơn ta ví dụ A, B, C là thread trong trạng thái chờ; rA, rB, rC là tài nguyên dùng chung và chỉ duy nhất một thread sử dụng trong một thời điểm, tài nguyên chỉ được giải phóng khi thread làm xong và không sử dụng nữa.
• A giữ rA, chờ rB
• B giữ rB, chờ rC
• C giữ rC, chờ rA
Từ ví dụ trên ta thấy A, B, C là những thread đang ở trong trạng thái chờ tài nguyên, tuy nhiên tài nguyên của chúng không bao giờ được giải phóng, và mãi mãi chúng cứ chờ thôi. Để hạn chế deadlock thì chúng ta phải đưa ra, cải tiến việc cấp phát, thu hồi tài nguyên cho các thread và hiện này có khá nhiều thuật toán cải tiến việc này, tuy nhiên không có cái nào giúp tránh hoàn toàn deadlock.
1.5 Những lớp tiện ích trong lập trình đa luồng (multithread)
Trong java đã xây dựng một vài lớp giúp hỗ trợ lập trình đa luồng, chúng được đặt trong gói Concurent như: Semaphore, Lock, Condition, ConcurrentMap, ConcurrentLinkedQueue, …
Semaphore là một lớp tiện ích đã xây dựng trong java (java 5) dùng để giới hạn số lượng thread truy cập đến tài nguyên. Đầu tiên thread cần yêu cầu sự chấp nhận truy cập tài nguyên từ semaphore bằng hàm acquire, và thread sẽ về trạng thái đợi (block) cho đến khi được chấp nhận, khi thread được chấp nhận nó thực hiện công việc của nó xong sẽ gọi hàm release để trả về cho semaphore một sự chấp nhận còn trống để semaphore cấp cho thread khác.
Lock là một interface được xây dựng nhằm mở rộng hơn việc quản lý truy cập tài nguyên trong nhiều thread hơn so với cách dùng synchronized chẳng hạn như có thể hỗ trợ nhiều điều kiện (condition object) trong lock, hoặc hỗ trợ nhiều thuật toán cho chain-lock để tránh deadlock tốt hơn, … Khi dùng Lock chúng ta nên theo một template chuẩn như sau:
Lock l = ...;
l.lock();
try {
// Các task truy cập tài nguyên bảo vệ bởi lock đặt ở đây
} finally {
l.unlock();
}
Condition là một interface dùng để nhóm cách xử lý trong các phương thức (wait, notify, notifyAll) của lớp Object vào thành một đối tượng nhằm đưa ra nhiều chọn lựa hơn trong việc đồng bộ, ví dụ chúng ta có thể dùng nhiều condition và khi nào chúng ta muốn đánh thức chính xác điều kiện nào được sử dụng tài nguyên.
Ví dụ ta dùng lại ví dụ Consumer-Producer ở trên, thêm vào 2 biến lock, condition và chỉnh sửa hai làm get và put thế là ta cũng được kết quả như dùng với từ khóa synchronized:
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
public int get() {
int result;
lock.lock();
try {
while(isRemainingContents == false) {
try {
condition.await();
} catch (InterruptedException e) {
}
}
isRemainingContents = false;
//print onto screen
System.out.println("\tConsumer get " + this.contents);
//get value
result = contents;
//Notify Producer to produce new contents
condition.signalAll();
} finally {
lock.unlock();
}
return result;
}
public void set(int piContents) {
lock.lock();
try {
while(isRemainingContents == true) {
try {
condition.await();
} catch (InterruptedException e) {
}
}
this.contents = piContents;
isRemainingContents = true;
//print onto screen
System.out.println("Producer set " + this.contents);
//Notify Consumer to eat this contents
condition.signalAll();
} finally {
lock.unlock();
}
}
Kết quả luôn luôn là thế này:
Producer set 0
Consumer get 0
Producer set 1
Consumer get 1
Producer set 2
Consumer get 2
Producer set 3
Consumer get 3
Producer set 4
Consumer get 4
Producer set 5
Consumer get 5
Producer set 6
Consumer get 6
Producer set 7
Consumer get 7
Producer set 8
Consumer get 8
Producer set 9
Consumer get 9
ConcurrentMap là một interface kế thừa từ Map dùng để cải tiến, tối ưu hơn khi chạy Map trong ứng dụng đa luồng (multithread)
Ví dụ cách dùng ConcurrentHashMap tương tự dùng Map, tuy nhiên người ta đã thống kê là nếu chương trình có càng nhiều thread truy cập vào cMap này thì dùng ConcurrentMap sẽ cho performance tốt hơn rất nhiều so với dùng Map với đồng bộ kiểu synchronized
public static ConcurrentMap<String,String> cMap =
new ConcurrentHashMap<String, String>(1000);
public static void main(String[] args) {
cMap.putIfAbsent("id", "1");
cMap.putIfAbsent("name", "kien");
cMap.putIfAbsent("email", "nakien2a@yahoo.com");
System.out.println(cMap); //Print to screen
}
Kết quả: {name=kien, email=nakien2a@yahoo.com, id=1}
NgoMinhTien20 (I11C)- Tổng số bài gửi : 17
Join date : 26/08/2011
Similar topics
» Thảo luận Bài 5 (Đa luồng)
» Thảo luận Bài 5
» Thảo luận Bài 5
» Thảo luận Bài 5
» Giới thiệu về Threads-lập trình đa luồng trong Java
» Thảo luận Bài 5
» Thảo luận Bài 5
» Thảo luận Bài 5
» Giới thiệu về Threads-lập trình đa luồng trong Java
Trang 1 trong tổng số 1 trang
Permissions in this forum:
Bạn không có quyền trả lời bài viết