Đối tượng khóa (Lock 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 394 - 398)

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

9.5.3. Đối tượng khóa (Lock Objects)

Có hai cơ chế đế bảo vệ một khối mã lệnh khỏi việc truy cập đồng thời. Ngôn ngữ Java cung cấp một từ khóa synchronized cho mục đích này và Java SE 5.0 giới thiệu lóp ReentrantLock. Từ khóa synchronized tự động cung cấp một khóa cũng như một “điều kiện' (condition) đi kèm. Điều này khiến nó trờ nên mạnh mẽ và thích họp cho hầu hết các trường họp yêu cầu khóa tường minh. Tuy nhiên, chúng tôi tin rằng sẽ dễ dàng hiểu từ khóa synchronized hơn sau khi bạn đã hiểu về khóa (lock) và điều kiện (condition) một cách độc lập. Gói java.util.concurrent cung cấp những lóp riêng biệt cho những cơ chế nền tảng này. Một khi bạn đã hiếu những cơ chế này, chúng tôi sẽ giới thiệu từ khóa synchronized.

Phác thảo cơ bản để bảo vệ một khối mã với một ReentrantLock là:

myLock.lockQ; // Mộtđốitượng ReentrantLock try

{

<khu vực quan trọng (critical section)>

} finally {

myLock-unlockQ; // Đảm bảo lock được khóa lại ngay cả khi một ngoại lệ được ném ra

}

cấu trúc này đảm bảo rằng chỉ có một luồng tại một thời điếm có thế đi vào khu vực quan trọng. Ngay khi một luồng khóa đối tượng lock, không có luồng khác có thể đi qua câu lệnh myLock.lock(). Khi những luồng khác gọi phương thức lock, chúng sẽ bị vô hiệu hóa cho đến khi luồng đầu tiênmởkhóa cho đối tượng lock.

LƯU Ý: Một điều tối quan trọng là thao tác unlock phải được để trong mệnh đề finally. Neu đoạn mã trong khu vực quan trọng ném ra một ngoại lệ, lock phải được mờ

khóa. Neu không, những luồng khác sẽ bịkhóa vĩnh viễn.

CHÚÝ: Khi bạn sử dụng khóa, bạn không thể sử dụng mệnh đề try-yvỉth-resources.

Đầu tiên, phương thức unlock không được gọi close. Nhưng ngay cả khi nó được đổi tên, mệnh đề try-with-resources cũng sẽ không hoạt động. Tiêu đề của nó mong muốn sự khai báo của một biến mới. Nhưng khi bạn sử dụng một khóa, bạn muốn vẫn dùng lại cùng biến đã được chia sẻ giữa các luồng.

Bây giờ, chúngtasẽ sử dụng một khóađể bảo vệ phương thức transfercủalópBank.

public class Bank{

private LockbankLock = new ReentrantLockQ; // ReentrantLock cài đặt giao diện Lock­

publicvoid transferfint from, int to, int amount){

bankLock.lockQ;

try{

System.out.print(Thread.currentThreadO);

accounts [from] -= amount;

System.out.printff' %10.2ffrom %d to%d”, amount, from, to);

accounts[to] += amount;

System.out.printff' Total Balance: %10.2f%n",getTotalBalanceQ);

}

finally {

bankLock.unlockQ;

} } }

Giả sử một luồng gọi phương thức transfer và bị buộc dừng (preempt) trước khi nó thực hiện xong. Giả sử một luồng thứ hai cũng gọi phương thức transfer. Luồng thứ hai không thể lấy được khóa (lock)và bị khóa/chặn trong lời gọi tới phươngthức lock. Nó bị vô hiệu hóa vàphải đợi luồng thức nhấthoàn thành xong phương thức transfer. Khi luồng thứ nhất mởkhóa lock, thì luồng thứ hai có thế được tiếnhành (xem hình 9.5).

Hãythử điều này. Thêm đoạn mã khóa vào phương thức transfer và chạy lạichương trình. Bạn có thế chạy chương trình mới này mãi mãi mà giá trị tống lượng tiền trong ngân hàng sẽ không bịsai lệch.

Chú ý rằng, mỗi đối tượng Bank có một đối tượng ReentrantLock của nó. Neu hai luồng cố gắng truy cập cùng một đối tượng Bank, thì khóa sẽ phục vụ để tuần tự hóa (serialize) quyền truy cập. Tuy nhiên, nếu hai luồng truy cập hai đối tượng Bank khác nhau, mỗi luồng sẽ yêu cầu một khóa khác nhau và không luồng nào bị chặn/khóa. Đeu này là có thể, do các luồng không can thiệp luồng khác khi chúng thao táctrên các thể hiện Bank khác nhau.

Khóa được gọi là reentrant bời vì một luồng có thế lặp lại yêu cầu một khóa mà nó đã sờ hữu. Khóa có một biến đếm hold count để lưu vết các lời gọi lồng nhau tới phương thức lock. Tiến trình phải gọi unlock cho mỗi lần gọi lock để từ bỏ/nhả (relinquish) khóa.

Do tính năng này, đoạn mã được bảo vệ bời một khóa cóthể gọi phương thức khác khi nó sử dụng cùng những khóagiống nhau.

Ví dụ, phương thức transfer gọi phương thức getTotalBalance, mà cũng khóa đối tượng bankLock và khiến đối tượng này có giá trị của hold count là 2. Khi thoát ra khỏi phương thức getTotalBalance, giá trị của hold count quay ngược ve 1. Khi thoát ra khỏi phương thức transfer, giá trị của hold count quay ve0 và luồng sẽ nhả khóa.

Hình 9.5. So sánh các luồng đồng bộ và không đồng bộ.

Nhìn chung, bạn sẽ muốn bảo vệ những khối lệnh cập nhật hoặc điều tra (inspect) một đối tuợng chia sẻ, để bạn có thể đảm bảo rằng những thao tác này hoàn thành việc chạy trước khi một luồng khác có thể sử dụng cùng đối tượng.

CHÚ Ý: Hãy cẩn thận đảm bảo rằng đoạn mã trong một khuvực quan trọng không bị vượt qua bằng việc ném ra một ngoại lệ. Neu một ngoại lệ được ném ra trước khi kết thúc một khu vực quan trọng, mệnh đề finally sẽ nhả khóa, nhưng đối tượng có thể đang trong trạng thái bị hư hại.

CHÚ Ý: Tính công bằng nghe có vẻ tốt, nhưng các khóa công bằng chậm hon rất nhiều so với các khóathôngthường. Bạn chỉ nên bậtkhóa công bằng nếu bạn thực sự biết vềviệc bạn đang làm và có một lýdo đặc biệt để xem xét bản chất công bằng của chương trình. Thậm chí, nếu bạn sử dụng một khóa công bằng, bạn cũng không có đảm bảo rằng trình lập lịch luồng là công bang. Neu trình lập lịch luồng chọn bỏ qua một luồng đã đợi khóa trong một thời gian dài, luồng đó không có cơ hội để được đối xử công bằng bởi khóa.

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 394 - 398)

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

(408 trang)