Блокировщики блокируют, пока 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 ...
}
...
}
}
Ответ 4
Вы можете использовать методы wait() и notify():
Простой пример:
http://www.java-samples.com/showtutorial.php?tutorialid=306
Ответ 5
Как я понимаю, одновременный материал создается явно, чтобы не ждать и делать то, что вы хотите сразу, но если это вообще возможно. В вашем случае вам нужно подождать, пока что-то не будет доступно, поэтому ваш единственный вариант, например, wait()
. Короче говоря, кажется, что способ, которым вы его описали, является единственным правильным способом.
Ответ 6
Если инициализация является сделкой с одним выстрелом, попробуйте java.util.concurrent.CountDownLatch.
Ответ 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();
}
}