在Java 9上发布数据以只有一个订阅者将使用它的方式流向订阅者

时间:2018-05-01 07:25:40

标签: java java-9 flow

有没有办法以只有一个订阅者会收到订阅者的方式向订阅者发布数据? 我想要实现的是订阅者发布者模型将作为具有多个读者但只有一个发布者的队列。 一旦发布者发布数据,接收它的第一个订阅者将是唯一将处理它的用户。

提前致谢!!!

2 个答案:

答案 0 :(得分:3)

在反应流中(至少在他们的java.util.concurrent.Flow版本中),订阅者只询问数据,只有发布者控制如何发布该数据。

Java 9中存在的唯一通用实现Flow.PublisherSubmissionPublisher,遵循标准的pub / sub方式将所有已发布项目发布给所有订阅者。我没有找到任何简单的方法来攻击SubmissionPublisher以使其仅发布给一个订阅者。

但您可以尝试编写自己的Flow.Publisher实现,如下所示:

class QueueLikePublisher<T> implements Publisher<T> {
    private final ExecutorService executor = ForkJoinPool.commonPool(); // daemon-based
    private List<QueueLikeSubscription<? super T>> subscriptions = new CopyOnWriteArrayList<>();

    public synchronized void subscribe(Subscriber<? super T> subscriber) {
        // subscribing: adding a new subscription to the list
        QueueLikeSubscription<? super T> subscription = new QueueLikeSubscription<>(subscriber, executor);
        subscriptions.add(subscription);
        subscriber.onSubscribe(subscription);
    }

    public void submit(T item) {
        // we got some data: looking for non-completed and demanding
        // subscription and give it the data item

        for (QueueLikeSubscription<? super T> subscription : subscriptions) {
            if (!subscription.completed && subscription.demand > 0) {
                subscription.offer(item);
                // we just give it to one subscriber; probaly offer() call needs
                // to be wrapped in a try/catch
                break;
            }
        }
    }

    static class QueueLikeSubscription<T> implements Subscription {
        private final Subscriber<? super T> subscriber;
        private final ExecutorService executor;
        volatile int demand = 0;
        volatile boolean completed = false;

        QueueLikeSubscription(Subscriber<? super T> subscriber,
                ExecutorService executor) {
            this.subscriber = subscriber;
            this.executor = executor;
        }

        public synchronized void request(long n) {
            if (n != 0 && !completed) {
                if (n < 0) {
                    IllegalArgumentException ex = new IllegalArgumentException();
                    executor.execute(() -> subscriber.onError(ex));
                } else {
                    // just extending the demand
                    demand += n;
                }
            }
        }

        public synchronized void cancel() {
            completed = true;
        }

        Future<?> offer(T item) {
            return executor.submit(() -> {
                try {
                    subscriber.onNext(item);
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            });
        }
    }
}

它将项目发布给尚未完成的第一个订阅者(例如,已取消)并且具有非零需求。

请注意,此代码只是用于演示的行政用途的大纲。例如,它可能包含更多异常处理(如处理RejectedExecutionException)。

答案 1 :(得分:1)

只有一个订阅者应该接收每个数据项的情况很常见。例如,订户可以是数据库连接和数据项 - 对数据库的请求,以及发布者 - 整个连接池的中心入口点。它是一种不同的数据交换协议,因此使用j.u.c.Flow中的接口可能会造成混淆。

通常,这些接口可用于此主工作者协议,但存在一个微妙但重要的区别:订阅者不应一次请求多个数据项。否则,一名工人可以带几件物品,而其他工人则无需工作。因此可以从界面中删除方法Subscription#request()。假设通过订阅行为,订户同意接受一个数据项。一旦该项目提交给订户,订户就会被取消订阅。这允许不扫描试图找到可接受订户的订阅列表(如在@Roman Puchkovskiy实现中),而是将下一个数据项提交给第一个订阅订户。一旦订户需要更多数据,它就会再次订阅。这正是线程池中的工作线程如何请求下一个任务。

由于方法cancel()仍然是Subscription中唯一的方法,我们可以用新方法Publisher#cancel(Subscriber)替换它,并完全消除Subscription接口。然后使用方法Subscriber#onSubscribe(Subscription)替换方法Subscriber#onSubscribe(Publisher)

我正在开发一个异步库(它还没有生产质量),其中包含用例的解决方案:class PickPoint