Các đối tượng điều kiện (Condition Objects)

Một phần của tài liệu Bài Giảng Lập Trình Java - Đại Học Thủy Lợi.pdf (Trang 398 - 404)

Chương 9. LẬP TRÌNH ĐỒNG THỜI

9.5.4. Các đối tượng điều kiện (Condition Objects)

Thông thường, một luồng đi vào trong một khu vực quan trọng chỉ khi khám phá ra rằng nó không thể tiến hành cho đến khi một điều kiện được thỏa mãn. Sử dụng một đối tượng điều kiện (condition object) đếquản lý các luồng đã yêu cầu một khóa nhưng không thể thực hiện công việc hữu ích. Trong phần này, chúng tôi giới thiệu cài đật của nhữngđối tượng điềukiệntrong thư viện Java. (Do những nguyên nhân lịch sử, những đối tượng điều kiệnthường được gọilà những biếnđiều kiện -condition variables).

Chúng ta sẽ “làm mịn” chưong trình mô phòng ngân hàng ờ trên. Chúng ta không muốn chuyến tiền ra khỏi một tài khoản mà không có quỹ de coverviệcchuyếnkhoảnnày.

Chú ý, chúngtakhông thể sử dụng mã giống như:

if (bank.getBalance(from) >= amount) bank.transfer(from, to, amount);

Hoàn toàn có thể rằng luồng hiện tại sẽ bị vôhiệu hóa ở giữa việc kiểm tra điều kiện thành công và lời gọi phương thức transfer:

if (bank.getBalance(from) >= amount)

// Luồng có thể bị vô hiệu hóa tạithờiđiểm này!

bank.transfer(from, to, amount);

Tại thời điểm luồng đang chạy lại, lượng tiền trongtài khoảnngânhàng có thể xuống dưới lượng bạn muốn rút ra. Bạn phải đảm bảo rằng không có luồng khác cóthể thay đối lượng tiền ở giữa thời điểm kiểm tra điều kiện và hành động chuyển khoản. Bạn làm điều này bằng cách bảo vệ cảviệc kiếm tra điều kiện và hànhđộng chuyếnkhoảnvới một khóa:

public void transferfint from, int to, int amount) {

bankLock.lock();

try{

while (accounts[from] < amount) { // Chờ (wait)

}

// Chuyển khoản (transfer funds) }

finally {

bankLock.unlockQ;

}__________________________________________________________________

Bâygiờ, chúng tasẽ làm gì khi không có đủ lượng tiền trong tài khoản? Chúng ta sẽ đợi cho đến khi một vài luồng khác đã thêm tiền vào tài khoản. Nhưng luồng này vừa giành được quyền truy cập độc quyền tới bankLock, vì vậy, không luồng nào khác có cơ hội để gửi tiền. Đây là lúc các đối tượng điều kiện được sử dụng.

Một đối tượng khóa có thể có một hoặc nhiều hơn đối tượng điều kiện đi kèm. Bạn lấy được một đối tượng điều kiện với phương thức newCondition. Bạn có thể cung cấp cho mỗi đối tượng điều kiện một tên gọi, giúp gợi lên điều kiện mà nó đại diện. Ví dụ, dưới đây là cách chúng ta thiết lập một đối tượng điều kiệnđểbiểu diễn điều kiện“đủ tiềrì\

class Bank {

private ConditionsufficientFunds;...

public BankQ {

sufficientFunds = bankLock.newConditionO;

}

}__________________________________________________________________

Neu phương thức transfer thấy rằng, lượng tiền đủ không có sẵn, nó sẽ gọi:

sufficientFunds. awaỉt();

Luồng hiện tại bây giờ bị vô hiệu hóa và phải từbỏ khóa. Điều này khiến cho luồng khác có thể, nhưchúngta hy vọng,tăng số dưtài khoản.

Có một sự khác biệt mấu chốt giữa một luồng đang đợi đế yêu cầu khóa và một luồng vừa gọiphương thức await. Khi một luồng gọi phương thức await, nó đi vào một tập các luồng đợi (wait set) cho điều kiện đó. Luồng không được thực hiện có thể chạy (runnable) khi khóa sẵn sàng. Thay vào đó, nó vẫn bị vô hiệu hóa cho đến khi luồngkhác gọi phương thức signalAll trên cùng điềukiện.

Khi một luồng khác đã chuyểntiền, nó nên gọi phương thức:

sufficientFunds.sỉgnalAll();

Lời gọi này kích hoạt lại tất cả các luồng đang đợi điều kiện. Khi những luồng được xóa bỏ khỏi tập đợi, chúng lại có thể chạy (runnable) và trình lập lịch cuối cùng sẽ kích hoạt lại chúng. Tại thời điểm đó, chúng sẽ cố gắng đi vào đối tượng. Ngay khi khóa sẵn sàng, một trong số chúng sẽ lấy được khóa và tiếp tục chạy tại vị trí nó dừng khi trước, trả

Tại thời điếm đó, luồng nên kiếm tra điều kiện một lần nữa. Không có đảm bảo rằng điều kiện bây giờ đã hoàn thành - phuong thức signallAll chỉ đơn thuần báo hiệu cho các luồng đang chờ rằng điều kiện thê đã đuợc hoàn thành tại thời điếm này và nên đuợc kiểm tra điều kiện một lần nữa.

LƯU Ý: Thông thường lời gọi tới phương thức await nên ở trong vòng lặp, giống như mẫu sau:

while(!(oktoproceed)) condition.awaitf);

Điềurất quan trọng là một vài luồng khác cuối cùng sẽ gọi phương thức signallAll.

Khi một luồng gọi await, không có cách nào để nó tựkích hoạtlại. Nó đặt niềm tin vào các luồng khác. Neu không có luồng khác kích hoạt lại các luồng đang chờ, các luồng đang chờ sẽ không bao giờ được chạy lại một lần nữa. Điều này có thế dẫn đến những tình huống khó chịu, được gọi là bế tắc (deadlock). Nếu tất cả các luồng khác bị chặn và luồng hoạt động cuối cùng gọi phương thức OMYZZZ mà không bỏ chặn một trong những luồng khác, nó cũng sẽ bị chận. Không còn luồng nào đế bỏ chặn những luồng khác và chương trình bị treo.

Khi nào thì bạn nên gọi phương thức signalAirĩ Quytắc chung là gọi signalAll bất kế khi nào trạng thái của một đối tượng thay đổi theo cách có thể tác động thuận lợi tới các luồng đang chờ. Ví dụ, bất cứ khi nào một số dư tài khoản thayđối, các luồng đang chờ sẽ được cấp một cơ hội khác để kiểm tra số dư. Trong ví dụ của chúng ta, chúng ta gọi signalAllkhi đãhoàntất việc chuyểntiền.

public void transferfint from, int to, int amount) { bankLock.lockQ;

try {

while (accounts[from] < amount) sufficientFunds.await();

IỊChuyểntiền (transfer funds) sufficientFunds.signalAHQ;

} finally {

bankLock.unlock();

} }

Lưu ý rằng, lời gọi đến phương thức signalAll không kích hoạt ngay lập tức một luồng đang chờ. Nó chỉ bỏ chặn các luồng đang chờ để chúng có thể cạnh tranh để đi vào vào đối tượng sau khi luồng hiện tại đãtừbỏ khóa.

Một phương thức khác, signal, chỉ bỏ chặn một luồng đơn từ tập chờ, được chọn ngẫu nhiên. Cách này hiệu quả hơn là bỏ chặn tất cả các luồng, nhưng sẽ có một mối nguy hiem. Neu luồng được chọn ngẫu nhiên thấy rằng nó vẫn không thể tiếp tục, nó sẽ bị chặn lại lần nữa. Neu không có luồng nào khác gọi lại phương thức signal, thì hệ thống sẽ bế tắc (deadlock).

LƯU Ý: Một luồng chỉ có thể gọi phương thức await, signalAll, hoặc signal trên một điều kiện nếu nó sở hữu khóa của điều kiện này.

Neu bạn chạy chương trình 9.7, bạn sẽ nhận thấy rằng không có gì sai. Tổng số dư tồn tại ở mức 100.000 đô la mãi mãi. Không có tài khoản nào có số dưâm. (Một lần nữa, nhấn Ctrl + c để chấm dứt chương trình.) Bạn cũng có thể nhận thấy rằng, chương trình chạy chậm hơn một chút - đây là cái giá bạn trả cho việc bổ sung thêm cơ chế đồng bộ hóa. Trong thực tế, việc sử dụng các điều kiện chính xác có thể khá mạo hiểm. Trước khi bạn bất đầu cài đặt các đối tượng điều kiện riêng bạn, bạn nên cân nhắc sử dụng một trong các cấu trúc được mô tả là “Trìnhđồngbộ hóa” (Synchronizers).

Chương trình 9.8

importjava.util.*;

import java.util.concurrentlocks.*;

// Lớp biểu diễn mộtngân hàng vớimột sốtàikhoản, sử dụngkhóa để truy cập tuần tự.

public class Bank{

private finaldouble[] accounts;

private Lock bankLock;

private Condition sufficientFunds;

/

**

* Khởi tạo lớp ngân hàng

* @param n: số lượngtàikhoản

* @param initialBalance: số dư ban đầu cho mỗi tài khoản

7

public Bankfintn, double initialBalance) {

accounts = new double [n];

Arrays.fill(accounts, initialBalance);

bankLock= newReentrantLockQ;

sufficientFunds = bankLockmewConditionQ;

} /

**

* Chuyểntiền từ tài khoản này sangtài khoản khác

* @param from: tài khoản nguồn

* @param to: tài khoản đích

* @param amount: lượngtiềncầnchuyển

*/

public void transferfint from, int to, double amount] throws InterruptedException

{

bankLock.lock();

try {

while (accounts[from] < amount]

sufficientFunds.awaitQ;

System.out.print(Thread.currentThread(]];

accounts [from] -= amount;

System.out.printf(" %10.2ffrom %dto %d", amount, from, to];

accounts [to] += amount;

System.out.printff" Total Balance: %10.2f%n", getTotalBalancef)];

sufficientFunds.signalAllf];

} finally {

bankLock.unlock(];

} }

**

* Lấy về tổng số dư của tất cả tài khoản

* ©return: trả về tổng sốdư

7

public double getTotalBalancef) {

bankLock.lock();

try {

double sum= 0;

for (double a: accounts) sum += a;

return sum;

} finally {

bankLock.unlock();

} } /**

* Lấy về số lượngtài khoản trong ngân hàng

* ©return: trả về sốlượng tài khoản

7

public intsize() {

return accounts.length;

} }

Một phần của tài liệu Bài Giảng Lập Trình Java - Đại Học Thủy Lợi.pdf (Trang 398 - 404)

Tải bản đầy đủ (PDF)

(408 trang)