类似的问题:
我有一个对象,我希望向库客户端(特别是脚本客户端)公开一个方法,如:
interface MyNiceInterface
{
public Baz doSomethingAndBlock(Foo fooArg, Bar barArg);
public Future<Baz> doSomething(Foo fooArg, Bar barArg);
// doSomethingAndBlock is the straightforward way;
// doSomething has more control but deals with
// a Future and that might be too much hassle for
// scripting clients
}
但我可用的原始“东西”是一组事件驱动的类:
interface BazComputationSink
{
public void onBazResult(Baz result);
}
class ImplementingThing
{
public void doSomethingAsync(Foo fooArg, Bar barArg, BazComputationSink sink);
}
其中ImplementingThing接受输入,做一些神秘的事情,比如在任务队列上排队,然后在结果发生时,sink.onBazResult()
在一个线程上被调用,该线程可能与ImplementingThing相同或不同。调用doSomethingAsync()。
有没有办法可以使用我拥有的事件驱动函数以及并发原语来实现MyNiceInterface,以便脚本客户端可以愉快地等待阻塞线程?
编辑:我可以使用FutureTask吗?
答案 0 :(得分:45)
使用您自己的Future实现:
public class BazComputationFuture implements Future<Baz>, BazComputationSink {
private volatile Baz result = null;
private volatile boolean cancelled = false;
private final CountDownLatch countDownLatch;
public BazComputationFuture() {
countDownLatch = new CountDownLatch(1);
}
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
if (isDone()) {
return false;
} else {
countDownLatch.countDown();
cancelled = true;
return !isDone();
}
}
@Override
public Baz get() throws InterruptedException, ExecutionException {
countDownLatch.await();
return result;
}
@Override
public Baz get(final long timeout, final TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
countDownLatch.await(timeout, unit);
return result;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean isDone() {
return countDownLatch.getCount() == 0;
}
public void onBazResult(final Baz result) {
this.result = result;
countDownLatch.countDown();
}
}
public Future<Baz> doSomething(Foo fooArg, Bar barArg) {
BazComputationFuture future = new BazComputationFuture();
doSomethingAsync(fooArg, barArg, future);
return future;
}
public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
return doSomething(fooArg, barArg).get();
}
解决方案在内部创建一个CountDownLatch,一旦收到回调就会被清除。如果用户调用get,则CountDownLatch用于阻塞调用线程,直到计算完成并调用onBazResult回调。 CountDownLatch将确保如果在调用get()之前发生回调,则get()方法将立即返回结果。
答案 1 :(得分:16)
嗯,有一个简单的解决方案,例如:
public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
final AtomicReference<Baz> notifier = new AtomicReference();
doSomethingAsync(fooArg, barArg, new BazComputationSink() {
public void onBazResult(Baz result) {
synchronized (notifier) {
notifier.set(result);
notifier.notify();
}
}
});
synchronized (notifier) {
while (notifier.get() == null)
notifier.wait();
}
return notifier.get();
}
当然,这假设您的Baz
结果永远不会为空...
答案 2 :(得分:12)
google guava library有一个易于使用的SettableFuture,这个问题非常简单(大约10行代码)。
public class ImplementingThing {
public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
try {
return doSomething(fooArg, barArg).get();
} catch (Exception e) {
throw new RuntimeException("Oh dear");
}
};
public Future<Baz> doSomething(Foo fooArg, Bar barArg) {
final SettableFuture<Baz> future = new SettableFuture<Baz>();
doSomethingAsync(fooArg, barArg, new BazComputationSink() {
@Override
public void onBazResult(Baz result) {
future.set(result);
}
});
return future;
};
// Everything below here is just mock stuff to make the example work,
// so you can copy it into your IDE and see it run.
public static class Baz {}
public static class Foo {}
public static class Bar {}
public static interface BazComputationSink {
public void onBazResult(Baz result);
}
public void doSomethingAsync(Foo fooArg, Bar barArg, final BazComputationSink sink) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Baz baz = new Baz();
sink.onBazResult(baz);
}
}).start();
};
public static void main(String[] args) {
System.err.println("Starting Main");
System.err.println((new ImplementingThing()).doSomethingAndBlock(null, null));
System.err.println("Ending Main");
}
答案 3 :(得分:4)
一个非常简单的例子,只是为了理解 CountDownLatch 而没有任何 额外的代码。
java.util.concurrent.CountDownLatch
是一个并发构造,允许一个或多个线程等待一组给定的操作完成。
使用给定计数初始化CountDownLatch
。通过调用countDown()
方法减少此计数。等待此计数达到零的线程可以调用其中一个await()
方法。调用await()
会阻塞线程,直到计数达到零。
下面是一个简单的例子。在Decrementer在countDown()
上调用CountDownLatch
3次后,等待的服务员将从await()
电话中释放。
您还可以提及一些TimeOut
等待。
CountDownLatch latch = new CountDownLatch(3);
Waiter waiter = new Waiter(latch);
Decrementer decrementer = new Decrementer(latch);
new Thread(waiter) .start();
new Thread(decrementer).start();
Thread.sleep(4000);
public class Waiter implements Runnable{
CountDownLatch latch = null;
public Waiter(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Waiter Released");
}
}
// --------------
public class Decrementer implements Runnable {
CountDownLatch latch = null;
public Decrementer(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
Thread.sleep(1000);
this.latch.countDown();
Thread.sleep(1000);
this.latch.countDown();
Thread.sleep(1000);
this.latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如果您不想使用CountDownLatch
,或者您的要求与Facebook类似且与功能不同。意味着如果调用一种方法,则不要调用另一种方法。
在这种情况下,您可以声明
private volatile Boolean isInprocessOfLikeOrUnLike = false;
然后你可以在方法调用的开头检查它是否为false
然后调用方法,否则返回..取决于你的实现。
答案 4 :(得分:4)
对于RxJava 2.x来说这很简单:
try {
Baz baz = Single.create((SingleEmitter<Baz> emitter) ->
doSomethingAsync(fooArg, barArg, result -> emitter.onSuccess(result)))
.toFuture().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
或没有Lambda表示法:
Baz baz = Single.create(new SingleOnSubscribe<Baz>() {
@Override
public void subscribe(SingleEmitter<Baz> emitter) {
doSomethingAsync(fooArg, barArg, new BazComputationSink() {
@Override
public void onBazResult(Baz result) {
emitter.onSuccess(result);
}
});
}
}).toFuture().get();
更简单:
Baz baz = Single.create((SingleEmitter<Baz> emitter) ->
doSomethingAsync(fooArg, barArg, result -> emitter.onSuccess(result)))
.blockingGet();
答案 5 :(得分:3)
根据Paul Wagland的回答,这是一个更通用的解决方案:
public abstract class AsyncRunnable<T> {
protected abstract void run(AtomicReference<T> notifier);
protected final void finish(AtomicReference<T> notifier, T result) {
synchronized (notifier) {
notifier.set(result);
notifier.notify();
}
}
public static <T> T wait(AsyncRunnable<T> runnable) {
final AtomicReference<T> notifier = new AtomicReference<>();
// run the asynchronous code
runnable.run(notifier);
// wait for the asynchronous code to finish
synchronized (notifier) {
while (notifier.get() == null) {
try {
notifier.wait();
} catch (InterruptedException ignore) {}
}
}
// return the result of the asynchronous code
return notifier.get();
}
}
以下是如何使用它的示例::
String result = AsyncRunnable.wait(new AsyncRunnable<String>() {
@Override
public void run(final AtomicReference<String> notifier) {
// here goes your async code, e.g.:
new Thread(new Runnable() {
@Override
public void run() {
finish(notifier, "This was a asynchronous call!");
}
}).start();
}
});
可以在此处找到更详细的代码版本:http://pastebin.com/hKHJUBqE
编辑: 与问题相关的例子是:
public Baz doSomethingAndBlock(final Foo fooArg, final Bar barArg) {
return AsyncRunnable.wait(new AsyncRunnable<Baz>() {
@Override
protected void run(final AtomicReference<Baz> notifier) {
doSomethingAsync(fooArg, barArg, new BazComputationSink() {
public void onBazResult(Baz result) {
synchronized (notifier) {
notifier.set(result);
notifier.notify();
}
}
});
}
});
}
答案 6 :(得分:1)
(最适合我的方法)最简单的方法是
轮询队列(在那里 您将其屏蔽)。
public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) throws InterruptedException {
final BlockingQueue<Baz> blocker = new LinkedBlockingQueue();
doSomethingAsync(fooArg, barArg, blocker::offer);
// Now block until response or timeout
return blocker.poll(30, TimeUnit.SECONDS);
}