2015-09-25 23 views
14

Tôi nghi ngờ rằng, trong ngôn ngữ Java, chúng ta cần giữ khóa, trước khi chúng tôi chờ đợi một số điều kiện để được thỏa mãn.Tại sao điều kiện Khóa chờ đợi phải giữ khóa

ví dụ, int khóa màn hình java:

synchronized(lock){ 
    System.out.println("before lock ..."); 
    lock.wait(); 
    System.out.println("after lock ..."); 
} 

hoặc utils cucurrent.

Lock lock = new ReentrantLock(); 
Condition cond = lock.newCondition(); 

lock.lock(); 
try{ 
    System.out.println("before condition ..."); 
    cond.await(); 
    System.out.println("after condition ..."); 
}catch(Exception e){ 
    e.printStackTrace(); 
}finally{ 
    lock.unlock(); 
} 

vậy, tại sao chúng ta không thể chờ đợi, mà không giữ khóa?

nếu điều đó chỉ vì Java, các công trình ngôn ngữ khác sẽ khác nhau?

Tôi hy vọng bạn có thể giải thích lý do sau khi thiết kế, nhưng không chỉ cho định nghĩa JAVA-SPEC.

+2

Java chạy trong ngữ cảnh của máy ảo và hành vi của máy ảo được chỉ định. –

+0

vì vậy, đó là vì SPEC? – Chinaxing

+2

Tất nhiên, đó là vì sự xác định. Câu hỏi lạ. – EJP

Trả lời

10

Hãy tưởng tượng bạn có thứ gì đó mà một chuỗi có thể cần phải đợi. Có lẽ bạn có một hàng đợi và một thread cần phải đợi cho đến khi có thứ gì đó trên hàng đợi để nó có thể xử lý nó. Hàng đợi phải an toàn cho luồng, vì vậy nó phải được bảo vệ bằng khóa. Bạn có thể viết mã sau:

  1. Mua khóa.
  2. Kiểm tra xem hàng đợi có trống không.
  3. Nếu hàng đợi trống, hãy chờ thứ gì đó được đặt trên hàng đợi.

Rất tiếc, điều đó sẽ không hoạt động. Chúng tôi giữ khóa trên hàng đợi để làm thế nào một sợi khác có thể đặt một thứ gì đó lên đó? Hãy thử lại:

  1. Mua khóa.
  2. Kiểm tra xem hàng đợi có trống không.
  3. Nếu hàng đợi trống, hãy nhả khóa và đợi thứ gì đó được đặt trên hàng đợi.

Rất tiếc, hiện tại chúng tôi vẫn gặp sự cố. Điều gì sẽ xảy ra nếu sau khi chúng tôi phát hành khóa nhưng trước khi chúng tôi chờ đợi thứ gì đó được đặt trên hàng đợi, thứ gì đó được đặt trên hàng đợi? Trong trường hợp đó, chúng tôi sẽ chờ đợi điều gì đó đã xảy ra.

Biến điều kiện tồn tại để giải quyết vấn đề chính xác này. Họ có một hoạt động "mở khóa và chờ" nguyên tử đóng cửa sổ này.

Vì vậy, chờ đợi phải giữ khóa vì nếu không sẽ không có cách nào để đảm bảo bạn không chờ đợi điều gì đó đã xảy ra. Bạn phải giữ khóa để ngăn chặn một thread khác đua với sự chờ đợi của bạn.

+0

Trong java, điều kiện hoặc object.wait chỉ được wakeup bởi sau khi thông báo hoặc tín hiệu. Vì vậy, giải thích của bạn là đúng. Tôi nghĩ rằng nếu chúng ta có thể để ngữ nghĩa của điều kiện như thế này: khi chờ/chờ, kiểm tra bit thức của nó (được thiết lập bởi thông báo/tín hiệu), nếu điều đó đúng, chỉ cần chờ đợi/chờ đợi (thay vì chờ đợi). và đối với chuỗi thông báo/tín hiệu, chỉ cần thiết lập bit điều khiển khi thông báo/tín hiệu, điều đó có nghĩa là điều kiện được thỏa mãn. nếu thiết kế như thế này, do đó, việc mua lại khóa là không cần thiết? – Chinaxing

+1

@Chinaxing Không, đừng làm thế. Xem xét: Hai luồng bị chặn trên biến điều kiện. Một chuỗi đặt một đối tượng trên hàng đợi, báo hiệu biến điều kiện và đánh thức một luồng. Một chuỗi đặt một đối tượng trên hàng đợi, báo hiệu biến điều kiện và đánh thức luồng khác. Chủ đề được đánh thức đầu tiên xử lý mục đầu tiên trên hàng đợi. Sau đó, trước khi luồng thứ hai có thể thực thi, luồng được đánh thức đầu tiên xử lý mục thứ hai trên hàng đợi. Bây giờ luồng thứ hai thực hiện, biến điều kiện đã được báo hiệu, nhưng hàng đợi rỗng. –

0

Câu trả lời đơn giản là vì nếu không bạn sẽ nhận được IllegalMonitorStateException được chỉ định trong Object.wait javadoc. Bên trong, đồng bộ hóa trong Java sử dụng cơ chế hệ điều hành bên dưới. Vì vậy, nó không chỉ là Java.

+0

Vâng, nhưng OP muốn biết _why_ bạn nhận được IllegalMonitorStateException. –

1

Xem tài liệu cho Condition.

Điều kiện giống như một hồ bơi chờ hoặc bộ chờ của một đối tượng và nó thay thế việc sử dụng các phương pháp theo dõi đối tượng (chờ, thông báo và thông báo cho tất cả). Các điều kiện cho phép một luồng tạm ngừng thực hiện (để "chờ") cho đến khi được thông báo bằng một luồng khác mà một số điều kiện trạng thái có thể là đúng. Một cá thể tình trạng được thực sự gắn kết với một khóa giống như các phương thức giám sát đối tượng yêu cầu khóa của đối tượng được chia sẻ để chờ hoặc thông báo. Vì vậy, trước khi gọi await() trên một điều kiện, thread phải khóa đối tượng Lock được sử dụng để tạo ra điều kiện. Khi phương thức await() được gọi, khóa kết hợp với điều kiện được giải phóng.

1

Nếu chuỗi chỉ đơn thuần chờ tín hiệu tiến hành thì có các cơ chế khác để thực hiện điều đó. Có lẽ có một số trạng thái được bảo vệ bởi khóa mà luồng đang chờ để vận hành và đáp ứng một số điều kiện. Để bảo vệ đúng trạng thái đó, sợi chỉ nên có khóa trước và sau khi đợi điều kiện, do đó, cần phải có sự thay đổi khóa.

8

Vâng, chúng ta còn chờ gì nữa? Chúng tôi đang chờ đợi một điều kiện để trở thành sự thật. Một luồng khác sẽ làm cho điều kiện đúng, sau đó thông báo cho các chuỗi chờ.

Trước khi chờ, chúng tôi phải kiểm tra xem điều kiện có sai không; kiểm tra này và sự chờ đợi phải là nguyên tử, tức là dưới cùng một khóa. Nếu không, nếu chúng ta chờ đợi trong khi điều kiện đã đúng, chúng tôi có thể sẽ không bao giờ thức dậy.

Vì vậy nó là cần thiết rằng các khóa đã được mua trước khi gọi wait()

synchronized(lock) 
{ 
    if(!condition) 
     lock.wait(); 

Nếu wait() tự động và âm thầm mua lại khóa, một nhiều lỗi sẽ không bị phát hiện.


Khi wakeup từ wait(), chúng ta phải kiểm tra điều kiện một lần nữa - không có gì bảo đảm rằng tình trạng này phải trở thành hiện thực ở đây (vì nhiều lý do - wakeup giả mạo; thời gian chờ, gián đoạn, nhiều người phục vụ, nhiều điều kiện)

synchronized(lock) 
{ 
    if(!condition) 
     lock.wait(); 
    if(!condition) // check again 
     ... 

Thông thường, nếu điều kiện vẫn sai, chúng tôi sẽ đợi lại. Do đó, mẫu điển hình là

while(!condition) 
     lock.wait(); 

Nhưng cũng có những trường hợp mà chúng tôi không muốn chờ lại.


Có bao giờ có trường hợp sử dụng hợp pháp ở trạng thái chờ/thông báo trần truồng?

synchronized(lock){ lock.wait(); } 

Chắc chắn; một ứng dụng có thể được tạo thành với việc chờ/thông báo trần truồng, với hành vi được xác định rõ; lập luận có thể được thực hiện rằng đây là hành vi mong muốn; và đây là cách triển khai tốt nhất cho hành vi đó.

Tuy nhiên, đó không phải là mẫu sử dụng điển hình và không có lý do gì để giải thích nó trong thiết kế API.

+0

Vâng, ngay cả với một "chờ đợi khỏa thân", chúng tôi có vấn đề mà 'chờ đợi' sẽ chỉ trở lại nếu một thread gọi' thông báo' * sau khi * chờ đợi 'đã bắt đầu. Nhưng không có cả hai chuỗi được đồng bộ hóa 'trên cùng một đối tượng, thậm chí không có mối quan hệ đặt hàng. Cần lưu ý rõ ràng rằng không chỉ không có bảo đảm rằng điều kiện đã trở thành sự thật, cũng không có bảo đảm rằng điều kiện đã không trở thành sai một lần nữa giữa thông báo và đánh thức. – Holger

Các vấn đề liên quan