调用者阻塞,直到getFoo()准备好了值?

时间:2010-05-14 19:47:23

标签: java multithreading properties synchronization blocking

我有一个Java Thread,它暴露了其他线程想要访问的属性:

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

问题是从运行到foo可用的时间很短。来电者可在此之前致电getFoo()并获取null。我宁愿他们只是阻塞,等待,并在初始化发生后获得值。 (foo之后永远不会改变。)在它准备好之前它将是几毫秒,所以我对这种方法很满意。

现在,我可以通过wait()notifyAll()实现这一目标,并且有95%的机会我会做得对。但我想知道你们都会怎么做; java.util.concurrent中有一个基元可以做到这一点,我错过了吗?

或者,你会如何构建它?是的,让foo变得不稳定。是的,在内部锁Object上进行同步并将检查置于while循环中,直到它不是null。我错过了什么吗?

9 个答案:

答案 0 :(得分:16)

如果foo仅初始化一次,则CountDownLatch非常合适。

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();
  }

}

锁存器提供与volatile关键字相同的可见性行为,这意味着读取线程将看到线程分配的foo的值,即使未声明foo volatile 1}}。

答案 1 :(得分:3)

通常,notify()和notifyAll()是您想要的方法。如果只有一个项目正在创建Foo并且许多线程可能会等待它,那么notify()是危险的。但我认为这里还有其他一些问题。

我不会让Thread成为存储Foo的地方。这样做意味着你必须在创建Foo之后保持一个线程。为什么不创建另一个对象来存储Foo,并让创建线程写入它?

然后我会得到getFoo()测试foo,只有等待它是非null(不要忘记将它与自身和foo setter同步)。

答案 2 :(得分:1)

试试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
     ...
   }
}

我认为wait / notifyAll不起作用,因为每个wait都会期望notify。您想要通知一次,然后再也不打扰通知,然后调用getFoo的任何其他线程将在foo初始化之前阻塞,或者如果已经初始化则只获取foo

答案 3 :(得分:1)

我会使用java.util.concurrent中的任何BlockingQueue更具体地说,如果有一个线程在等待Foo而另一个线程正在生成它,我会在案例中使用SynchronousQueue对于更多生产者和/或更多消费者,我的默认选项是LinkedBlockingQueue,但其他实现可能更适合您的应用程序。然后你的代码变成:

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 ...
     }
     ...
   }
}

答案 4 :(得分:0)

您可以使用wait()和notify()方法:

这里有一个简单的例子:

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

答案 5 :(得分:0)

根据我的理解,明确创建并发的东西不要等待并立即做你想做的事 - 但是如果可能的话。在你的情况下,你需要等到可用的东西,所以你唯一的选择是,呃,wait()。简而言之,似乎你描述它的方式是唯一正确的方法。

答案 6 :(得分:0)

如果初始化是一次性交易,请尝试java.util.concurrent.CountDownLatch

答案 7 :(得分:0)

延迟初始化是一个选项吗?

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

答案 8 :(得分:0)

也许尝试我喜欢的FutureValue类......

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();
}
}