我有一个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
。我错过了什么吗?
答案 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)
答案 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();
}
}