繁体   English   中英

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

[英]Publishing data on java 9 Flow to subscribers in a way that only one subscriber will consume it

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

提前致谢 !!!

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

Java 9中存在的Flow.Publisher的唯一通用实现是SubmissionPublisher ,它遵循将所有已发布项目发布给所有订阅者的标准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 )。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM