Блокировщики блокируют, пока getFoo() не будет иметь значение?

У меня есть 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 volatile. Да, синхронизируйтесь на внутренней блокировке Object и поместите проверку в цикл while, пока она не будет null. Я что-то пропустил?

Ответ 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

В целом уведомления() и notifyAll() - это нужные вам методы. notify() является опасным, если только один элемент создает Foo, и многие потоки могут ждать его. Но я думаю, что здесь есть и другие проблемы.

Я бы не стал использовать Thread для хранения Foo. Это значит, что после создания Foo вам нужно поддерживать поток. Почему бы не создать другой объект для хранения Foo и создать для него поток создания?

Тогда я бы получил getFoo() test foo и только дождался, если он был не нулевым (не забудьте синхронизировать его с собой и с установщиком foo).

Ответ 3

Я бы использовал любой BlockingQueue в java.util.concurrent В частности, если есть один поток, ожидающий 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 ...
     }
     ...
   }
}

Ответ 5

Как я понимаю, одновременный материал создается явно, чтобы не ждать и делать то, что вы хотите сразу, но если это вообще возможно. В вашем случае вам нужно подождать, пока что-то не будет доступно, поэтому ваш единственный вариант, например, wait(). Короче говоря, кажется, что способ, которым вы его описали, является единственным правильным способом.

Ответ 7

Является ли ленивая инициализация опцией?

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

Ответ 8

Попробуйте 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 ожидает a notify. Вы хотите уведомить один раз, а затем никогда не беспокоиться о уведомлении, тогда любой другой поток, вызывающий getFoo, будет либо блокироваться до тех пор, пока 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();
}
}