【问题标题】:Callers block until getFoo() has a value ready?调用者阻塞直到 getFoo() 准备好一个值?
【发布时间】:2010-05-14 19:47:23
【问题描述】:

我有一个 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。我错过了什么吗?

【问题讨论】:

    标签: java multithreading properties synchronization blocking


    【解决方案1】:

    如果 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

    【讨论】:

      【解决方案2】:

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

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

      然后我会让 getFoo() 测试 foo 并且仅在它为非 null 时等待(不要忘记将其与自身和 foo 设置器同步)。

      【讨论】:

      • 同意,我希望不是这样。在这种情况下,我真的必须在 run() 中创建对象。这是一个有奇怪需求的 GUI 应用程序。
      【解决方案3】:

      我会在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 ...
           }
           ...
         }
      }
      

      【讨论】:

      • SynchronousQueue 在许多情况下是一个方便的构造,但它并不能很好地满足这里的要求:foo 只被初始化一次,并且可能被读取多次。这将只允许foo 被读取一次。其他读取该属性的尝试将永远阻塞。
      • 是的,你是对的,我错过了“仅初始化一次”部分。我认为这更像是生产者/消费者的情况。
      【解决方案4】:

      试试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(如果它已经初始化)。

      【讨论】:

      • 实际上,这里有一个微妙的错误:由于latch 不是finalvolatile,因此不能保证对其他线程“可见”。在实践中可能不是问题,但保证并不真正花费任何费用。
      【解决方案5】:

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

      这里是一个简单的例子:

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

      【讨论】:

      • 当然,我可以手动推出解决方案。我认为这比这要复杂一点——该字段应该是volatile,我们需要notifyAll() 才能开始——所以我希望cmets 能够了解我还需要什么,或者是一个绝对正确的预打包解决方案。
      • @Sean 在那种情况下,我会接受 DJClayworth 的建议
      【解决方案6】:

      据我了解,并发的东西是明确创建的,不是等待并立即做你想做的事——但如果有可能的话。在你的情况下,你需要等到有东西可用,所以你唯一的选择是,呃,wait()。总之,看来你描述的方式是唯一正确的方式。

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          延迟初始化是一种选择吗?

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

          【讨论】:

          • 在这种情况下不是,因为 foo 必须在另一个线程中进行。
          【解决方案9】:

          也许试试我喜欢的 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();
          }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-06-23
            • 2016-05-31
            • 2023-04-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多