2010-05-14 26 views
10

Tôi có một Java Thread đó cho thấy một tài sản mà chủ đề khác muốn truy cập:Chặn người gọi cho đến khi getFoo() có giá trị sẵn sàng chưa?

class MyThread extends Thread { 
    private Foo foo; 
    ... 
    Foo getFoo() { 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    ... 
    } 
} 

Vấn đề là phải mất một thời gian ngắn kể từ thời điểm này sẽ kéo dài tới foo có sẵn. Người gọi có thể gọi số getFoo() trước số này và nhận số null. Tôi muốn họ chỉ đơn giản là chặn, chờ đợi, và nhận được giá trị khi khởi tạo đã xảy ra. (foo là không bao giờ thay đổi sau đó.) Nó sẽ là một vấn đề của mili giây cho đến khi nó đã sẵn sàng, vì vậy tôi cảm thấy thoải mái với cách tiếp cận này.

Bây giờ, tôi có thể thực hiện điều này với wait()notifyAll() và có 95% cơ hội tôi sẽ làm đúng. Nhưng tôi tự hỏi làm thế nào tất cả các bạn sẽ làm điều đó; là có một nguyên thủy trong java.util.concurrent mà sẽ làm điều này, mà tôi đã bỏ lỡ?

Hoặc, bạn sẽ cấu trúc nó như thế nào? Có, làm cho foo dễ bay hơi. Có, đồng bộ hóa trên khóa nội bộ Object và đặt séc trong vòng lặp while cho đến khi không phải là null. Tôi có thiếu gì không?

Trả lời

14

Nếu foo được khởi tạo chỉ một lần, thì CountDownLatch phù hợp nhất.

class MyThread extends Thread { 

    private final CountDownLatch latch = new CountDownLatch(1); 

    ... 

    Foo getFoo() throws InterruptedException 
    { 
    latch.await(); /* Or use overload with timeout parameter. */ 
    return foo; 
    } 

    @Override 
    public void run() { 
    foo = makeTheFoo() 
    latch.countDown(); 
    } 

} 

Van kéo cung cấp các hành vi tầm nhìn giống như từ khóa volatile, có nghĩa là bài đọc sẽ thấy giá trị của foo bởi thread giao, mặc dù foo không được khai báo volatile.

+0

bạn đang nhanh chóng! LOL – Kiril

0

Bạn có thể sử dụng các wait() và notify() phương pháp:

đơn giản ví dụ ở đây:

http://www.java-samples.com/showtutorial.php?tutorialid=306

+0

Chắc chắn, tôi có thể cuộn một giải pháp bằng tay. Tôi nghĩ nó phức tạp hơn thế này - trường phải là 'dễ bay hơi' và chúng ta cần 'notifyAll()' để bắt đầu - vì vậy tôi đã hy vọng nhận xét về những thứ khác tôi cần, hoặc giải pháp được đóng gói sẵn chắc chắn đúng. –

+0

@Sean Trong trường hợp đó tôi sẽ đi với đề nghị của DJClayworth –

0

Theo tôi được biết, công cụ đồng thời được tạo ra một cách rõ ràng không phải chờ đợi và làm những gì bạn muốn ngay lập tức - nhưng nếu nó có thể xảy ra. Trong trường hợp của bạn, bạn cần đợi cho đến khi có thứ gì đó, do đó, tùy chọn duy nhất của bạn là, uh, đến wait(). Trong ngắn hạn, có vẻ như cách bạn mô tả nó là cách chính xác duy nhất.

3

Thông báo chung() và thông báoAll() là các phương pháp bạn muốn. notify() là nguy hiểm nếu chỉ có một mục đang tạo Foo và nhiều luồng có thể đợi nó. Nhưng tôi nghĩ có một số vấn đề khác ở đây.

Tôi sẽ không đặt Chủ đề là nơi lưu trữ Foo. Làm như vậy có nghĩa là bạn phải giữ một chuỗi xung quanh sau khi Foo được tạo. Tại sao không làm cho một đối tượng khác để lưu trữ Foo, và có tạo ra thread ghi vào nó?

Sau đó, tôi sẽ có getFoo() kiểm tra foo và chỉ chờ đợi nếu nó là không null (đừng quên đồng bộ hóa nó với chính nó và với foo setter).

+0

+1 Để trích xuất foo từ MyThread –

+0

Đồng ý, tôi ước nó không phải như vậy. Trong trường hợp này, tôi thực sự phải tạo đối tượng trong run(). Đó là một ứng dụng GUI với những nhu cầu kỳ lạ. –

0

Khởi tạo lười biếng là một tùy chọn?

synchronized Foo getFoo() { 
    if (foo == null) 
     foo = makeFoo(); 
    } 
    return foo; 
} 
+0

Không phải trong trường hợp này kể từ khi foo phải được thực hiện trong chủ đề khác. –

0

Thử CountDownLatch:

class MyThread extends Thread { 
    private volatile CountDownLatch latch; 
    private Foo foo; 
    MyThread(){ 
     latch = new CountDownLatch(1); 
    } 
    ... 
    Foo getFoo() { 
    latch.await(); // waits until foo is ready 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    latch.countDown();// signals that foo is ready 
    ... 
    } 
} 

Tôi không nghĩ wait/notifyAll sẽ làm việc, vì mỗi wait sẽ mong đợi một notify. Bạn muốn thông báo một lần và sau đó không bao giờ bận tâm với thông báo nữa, thì bất kỳ chủ đề nào khác đang gọi getFoo sẽ chặn cho đến khi foo được khởi tạo hoặc chỉ nhận được foo nếu nó đã được khởi tạo.

+0

Trên thực tế, có một lỗi nhỏ ở đây: vì 'latch' không phải là' final' hoặc 'volatile', nó không được bảo đảm là" hiển thị "với các chủ đề khác. Có thể không phải là một vấn đề trong thực tế, nhưng bảo lãnh không thực sự chi phí bất cứ điều gì. – erickson

1

Tôi muốn sử dụng bất kỳ BlockingQueue 's trong java.util.concurrent Cụ thể hơn, nếu có một thread chờ Foo và một sản xuất nó, tôi muốn sử dụng một SynchronousQueue trong trường hợp nhiều nhà sản xuất và/hoặc người tiêu dùng nhiều lựa chọn mặc định của tôi là LinkedBlockingQueue, nhưng các triển khai khác có thể phù hợp hơn với ứng dụng của bạn. Mã của bạn sau đó trở thành:

class MyThread extends Thread { 
    private SynchronousQueue<Foo> queue = new SynchronousQueue<Foo>(); 
    ... 
    Foo getFoo() { 
    Foo foo; 
    try { 
     foo = queue.take(); 
    } 
    catch (InteruptedException ex) { 
     ...stuff ... 
    } 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    try { 
     queue.put(foo); 
    } 
    catch (InteruptedException ex) { 
     ...stuff ... 
    } 
    ... 
    } 
} 
+0

SynchronousQueue là một cấu trúc tiện dụng trong nhiều trường hợp, nhưng nó không phù hợp với yêu cầu ở đây rất tốt: 'foo' chỉ được khởi tạo một lần và có thể đọc nhiều lần. Điều này sẽ chỉ cho phép 'foo' được đọc một lần. Các nỗ lực khác để đọc tài sản sẽ chặn mãi mãi. – erickson

+0

Có, bạn đã chính xác, tôi đã bỏ lỡ phần "chỉ được khởi tạo một lần". Tôi nghĩ đây là tình huống của nhà sản xuất/người tiêu dùng. –

0

Có thể thử lớp FutureValue ưa thích của tôi ...

import java.util.concurrent.CountDownLatch; 

public class FutureValue<T> { 
private CountDownLatch latch = new CountDownLatch(1); 
private T value; 

public void set(T value) throws InterruptedException, IllegalStateException { 
    if (latch.getCount() == 0) { 
     throw new IllegalStateException("Value has been already set."); 
    } 
    latch.countDown(); 
    this.value = value; 
} 

/** 
* Returns the value stored in this container. Waits if value is not available. 
* 
* @return 
* @throws InterruptedException 
*/ 
public T get() throws InterruptedException { 
    latch.await(); 
    return value; 
} 

} 

// Usage example 
class MyExampleClass { 
@SuppressWarnings("unused") 
private static void usageExample() throws InterruptedException { 
    FutureValue<String> futureValue = new FutureValue<>(); 

    // the thread that will produce the value somewhere 
    new Thread(new Runnable() { 

     @Override 
     public void run() { 
      try { 
       futureValue.set("this is future"); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    }).run(); 

    String valueProducedSomewhereElse = futureValue.get(); 
} 
} 
Các vấn đề liên quan