2011-08-30 43 views
9

Với mã Java sau:Java wait()/join(): Tại sao điều này không bế tắc?

public class Test { 

    static private class MyThread extends Thread { 
     private boolean mustShutdown = false; 

     @Override 
     public synchronized void run() { 
      // loop and do nothing, just wait until we must shut down 
      while (!mustShutdown) { 
       try { 
        wait(); 
       } catch (InterruptedException e) { 
        System.out.println("Exception on wait()"); 
       } 
      } 
     } 

     public synchronized void shutdown() throws InterruptedException { 
      // set flag for termination, notify the thread and wait for it to die 
      mustShutdown = true; 
      notify(); 
      join(); // lock still being held here, due to 'synchronized' 
     } 
    } 

    public static void main(String[] args) { 
     MyThread mt = new MyThread(); 
     mt.start(); 

     try { 
      Thread.sleep(1000); 
      mt.shutdown(); 
     } catch (InterruptedException e) { 
      System.out.println("Exception in main()"); 
     } 
    } 
} 

Chạy này sẽ chờ trong một giây và sau đó thoát ra đúng cách. Nhưng điều đó là bất ngờ đối với tôi, tôi mong đợi một khóa chết xảy ra ở đây.

Lý do của tôi như sau: MyThread mới được tạo sẽ thực thi run(), được khai báo là 'đồng bộ', để nó có thể gọi wait() và đọc an toàn 'mustShutdown'; trong suốt cuộc gọi wait() đó, khóa được giải phóng và thu hồi lại khi quay trở lại, như được mô tả trong tài liệu về wait(). Sau một giây, thread chính thực thi shutdown(), một lần nữa được đồng bộ hóa để không truy cập vào mustShutdown cùng lúc khi nó được đọc bởi luồng khác. Sau đó nó đánh thức luồng khác bằng cách thông báo() và chờ hoàn thành thông qua join().

Nhưng theo ý kiến ​​của tôi, không có cách nào mà các chủ đề khác có thể trở về từ wait(), vì nó cần phải lấy lại khóa trên đối tượng thread trước khi trở về. Nó không thể làm như vậy bởi vì shutdown() vẫn giữ khóa trong khi bên trong join(). Tại sao nó vẫn hoạt động và thoát đúng cách?

+0

Đó là vì các tác dụng phụ như thế này kéo dài trực tiếp Chủ đề được cau mày. Bạn nên thực hiện một Runnable mà bạn quấn với một Thread. –

Trả lời

7

join() phương pháp nội bộ gọi wait() mà sẽ cho kết quả phát hành của khóa (của đối tượng Thread).

Xem mã join() dưới đây:

public final synchronized void join(long millis) 
    throws InterruptedException { 
    .... 
    if (millis == 0) { 
     while (isAlive()) { 
     wait(0); //ends up releasing lock 
     } 
    } 
    .... 
} 

Lý do tại sao mã của bạn thấy điều này và không thấy nói chung:: Lý do tại sao mã của bạn thấy điều này và không được không được quan sát nói chung, là bởi vì phương thức join() chờ đợi() trên Đối tượng chủ đề chính nó và do đó từ bỏ khóa đối tượng Thread và phương thức run() của bạn cũng đồng bộ hóa trên cùng một đối tượng Thread, bạn thấy kịch bản không mong muốn này.

+1

Điều đó thực sự giải thích nó. Nhưng như Paŭlo Ebermann đã chỉ ra, tài liệu không nói gì về điều này. Tôi có thể dựa vào tham gia() phát hành khóa không? – jlh

+0

Hoàn toàn có. Mã không thay đổi kể từ khi tôi nhấn tuổi dậy thì. Lo lắng không. –

+0

Rất tốt! Nhiều đánh giá cao, cảm ơn! – jlh

1

Việc triển khai Thread.join sử dụng tính năng chờ, cho phép khóa khóa, đó là lý do tại sao nó không ngăn chặn luồng khác lấy khóa.

Dưới đây là một mô tả từng bước của những gì xảy ra trong ví dụ này:

Bắt đầu thread MyThread trong các kết quả phương pháp chính trong một thread mới thực hiện phương pháp MyThread chạy. Chủ đề chính ngủ trong một giây, cho phép nhiều thời gian để khởi động và lấy khóa trên đối tượng MyThread.

Chủ đề mới có thể nhập phương thức chờ và nhả khóa của nó. Tại thời điểm này, luồng mới không hoạt động, nó sẽ không cố gắng lấy lại khóa cho đến khi nó được đánh thức. Chủ đề không trả lại từ phương thức chờ.

Tại thời điểm này, chủ đề chính thức dậy từ chế độ ngủ và tắt cuộc gọi trên đối tượng MyThread. Không có vấn đề gì khi lấy khóa vì luồng mới đã giải phóng nó khi nó bắt đầu chờ đợi. Các cuộc gọi chủ đề chính thông báo ngay bây giờ. Nhập phương thức kết nối, chủ đề chính sẽ kiểm tra xem luồng mới vẫn còn hoạt động, sau đó đợi, nhả khóa.

Thông báo xảy ra khi chủ đề chính nhả khóa. Vì chuỗi mới nằm trong bộ chờ cho khóa tại thời điểm luồng chính được gọi là thông báo, luồng mới nhận được thông báo và đánh thức. Nó có thể có được khóa, để lại phương thức chờ đợi, và kết thúc thực hiện phương thức chạy, cuối cùng phát hành khóa.

Việc chấm dứt chuỗi mới khiến tất cả các chuỗi đang chờ trên khóa của nó sẽ nhận được thông báo. Điều này đánh thức chủ đề chính, nó có thể có được khóa và kiểm tra xem luồng mới đã chết chưa, sau đó nó sẽ thoát khỏi phương thức kết nối và hoàn thành việc thực hiện.

/** 
* Waits at most <code>millis</code> milliseconds for this thread to 
* die. A timeout of <code>0</code> means to wait forever. 
* 
* @param  millis the time to wait in milliseconds. 
* @exception InterruptedException if any thread has interrupted 
*    the current thread. The <i>interrupted status</i> of the 
*    current thread is cleared when this exception is thrown. 
*/ 
public final synchronized void join(long millis) 
throws InterruptedException { 
long base = System.currentTimeMillis(); 
long now = 0; 

if (millis < 0) { 
     throw new IllegalArgumentException("timeout value is negative"); 
} 

if (millis == 0) { 
    while (isAlive()) { 
    wait(0); 
    } 
} else { 
    while (isAlive()) { 
    long delay = millis - now; 
    if (delay <= 0) { 
     break; 
    } 
    wait(delay); 
    now = System.currentTimeMillis() - base; 
    } 
} 
} 
0

Để bổ sung cho các câu trả lời khác: Tôi không thấy đề cập đến việc join() phát hành bất kỳ khóa nào trong tài liệu API, do đó hành vi này thực sự là cụ thể cho việc triển khai.

Tìm hiểu từ này:

  • không phân lớp Thread, thay vì sử dụng một thực hiện Runnable truyền cho đối tượng thread của bạn.
  • không đồng bộ hóa/chờ/thông báo về các đối tượng bạn không "sở hữu", ví dụ: nơi bạn không biết ai khác có thể đồng bộ hóa/chờ/thông báo trên đó.
+0

Thực hiện Runnable thay vì subclassing Thread dường như phức tạp hơn với tôi trong tình huống cụ thể này ... Và chỉ MyThread sử dụng 'sync', wait(), join() và notify(). Lớp chính không bao giờ sử dụng nó. (Mặc dù thread chính.) Vì vậy, tôi không chắc chắn về điểm bullet thứ hai của bạn. – jlh

+0

Điểm là 'join' nội bộ sử dụng' wait() ', mà bạn có thể không mong đợi. I E. màn hình của đối tượng 'MyThread' được sử dụng cho hai mục đích: Để đồng bộ hóa chu kỳ chạy/tắt của riêng bạn và để đồng bộ hóa quản lý luồng nội bộ. –

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