Вызывающие блокируют, пока getFoo () не будет иметь значение готово? - PullRequest
11 голосов
/ 14 мая 2010

У меня есть 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 ]

16 голосов
/ 15 мая 2010

Если 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.

3 голосов
/ 15 мая 2010

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

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

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

1 голос
/ 15 мая 2010

Я бы использовал любой из BlockingQueue в java.util.concurrent Более конкретно, если бы один поток ожидал Foo, а другой создавал его, я бы использовал * 1006.* в случае большего количества производителей и / или большего количества потребителей мой вариант по умолчанию - 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 ...
     }
     ...
   }
}
1 голос
/ 15 мая 2010

Попробуйте 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, если он уже инициализирован.

0 голосов
/ 20 сентября 2017

Может быть, попробуйте мой класс 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();
}
}
0 голосов
/ 15 мая 2010

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

synchronized Foo getFoo() {
    if (foo == null)
        foo = makeFoo();
    }
    return foo;
}
0 голосов
/ 15 мая 2010

Если инициализация сделана одним выстрелом, попробуйте java.util.concurrent.CountDownLatch .

0 голосов
/ 14 мая 2010

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

0 голосов
/ 14 мая 2010

Вы можете использовать методы wait () и notify ():

Простой пример здесь:

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

...