Різниця між потоками Java 8 та спостережуваними RxJava


144

Чи спостерігаються потоки Java 8, схожі на RxJava?

Визначення потоку Java 8:

Класи в новому java.util.streamпакеті забезпечують API Stream для підтримки операцій у функціональному стилі над потоками елементів.


8
FYI є пропозиції ввести більше подібних класів RxJava в JDK 9. jsr166-concurrency.10961.n7.nabble.com/…
Джон Vint

@JohnVint Який статус цієї пропозиції. Чи насправді це займе політ?
ІгорГанапольський

2
@IgorGanapolsky О так, це, безумовно, схоже, що він перетворить його на jdk9. cr.openjdk.java.net/~martin/webrevs/openjdk9/… . Є навіть порт для RxJava для Flow github.com/akarnokd/RxJavaUtilConcurrentFlow .
Джон Вінт

Я знаю, що це дуже старе питання, але я нещодавно взяв участь у цій чудовій бесіді Венката Субраманія, яка проникливо сприймає цю тему і оновлюється до Java9: youtube.com/watch?v=kfSSKM9y_0E . Може бути цікавим людям, що заглиблюються в RxJava.
Педро

Відповіді:


152

TL; DR : Усі послідовності / потокові версії для обробки потоків пропонують дуже подібний API для побудови трубопроводу. Відмінності полягають у API для обробки багатопотокової рідини та складу трубопроводів.

RxJava сильно відрізняється від Stream. З усіх речей JDK, найбільш близьким до rx.Observable є, мабуть, комбінація java.util.stream.Collector Stream + CompletableFuture (що коштує за рахунок вирішення додаткового рівня монади, тобто для обробки конверсії між Stream<CompletableFuture<T>>та CompletableFuture<Stream<T>>).

Існують значні відмінності між спостережуваним та потоковим:

  • Потоки базуються на тязі, спостереження - на основі поштовху. Це може здатися занадто абстрактним, але це має суттєві наслідки, які є дуже конкретними.
  • Потік можна використовувати лише один раз, спостерігати можна підписуватися багато разів
  • Stream#parallel()розбиває послідовність на розділи, Observable#subscribeOn()а Observable#observeOn()не; важко емулювати Stream#parallel()поведінку за допомогою Observable, у неї колись був .parallel()метод, але цей метод викликав стільки плутанини, що .parallel()підтримка була перенесена в окремий сховище на github, RxJavaParallel. Більше деталей - в іншій відповіді .
  • Stream#parallel()не дозволяє вказати пул потоків для використання, на відміну від більшості методів RxJava, що приймають необов'язковий Scheduler. Оскільки всі екземпляри потоку в JVM використовують один і той же пул fork-join, додавання .parallel()може випадково вплинути на поведінку в іншому модулі вашої програми
  • Потокам бракує операцій, пов'язаних з часом, як Observable#interval(), Observable#window()і багато інших; Це здебільшого тому, що потоки засновані на тязі, а висхідний потік не контролює, коли слід випускати наступний елемент за течією
  • Потоки пропонують обмежений набір операцій порівняно з RxJava. Напр. Потокам не вистачає операцій з відключенням ( takeWhile(), takeUntil()); Обхід використання Stream#anyMatch()обмежений: це термінальна робота, тому ви не можете використовувати її більше одного разу в потоці
  • Станом на JDK 8, немає жодної операції Stream # zip, що іноді буває досить корисно
  • Потоки важко побудувати самостійно. Спостережуване можна побудувати багатьма способами EDIT: Як зазначається в коментарях, існують способи побудови потоку. Однак, оскільки немає нетермінального короткого замикання, ви не можете, наприклад, легко генерувати потік рядків у файлі (JDK надає файли № рядків та BufferedReader # рядки поза коробкою, і іншими подібними сценаріями можна керувати, побудувавши Stream від Ітератора).
  • Спостережувані пропозиції управління ресурсами ( Observable#using()); ви можете обернути потік IO або mutex з ним і бути впевненим, що користувач не забуде звільнити ресурс - він буде розміщений автоматично після припинення підписки; У потоці є onClose(Runnable)метод, але вам доведеться викликати його вручну або за допомогою пробних ресурсів. E. g. ви повинні мати на увазі, що файли № рядків () повинні бути укладені у блок "пробні ресурси".
  • Спостереження синхронізуються наскрізь (я насправді не перевіряв, чи те саме стосується Потоків). Це позбавляє вас думати про те, чи основні операції безпечні для потоків (відповідь завжди "так", якщо немає помилки), але накладні витрати, пов'язані з паралельною валютою, будуть там, незалежно від того, потрібен ваш код чи ні.

Раунд: RxJava значно відрізняється від потоків. Справжніми альтернативами RxJava є інші реалізації ReactiveStreams , наприклад відповідна частина Akka.

Оновлення . Існує хитрість використання пулу fork-join за замовчуванням для Stream#parallel, див. Спеціальний пул потоків у паралельному потоці Java 8

Оновлення . Все вищезазначене базується на досвіді роботи з RxJava 1.x. Тепер, коли RxJava 2.x тут , ця відповідь може бути застарілою.


2
Чому потоки важко побудувати? Згідно з цією статтею, це здається простим: oracle.com/technetwork/articles/java/…
ІгорГанапольський

2
Існує досить багато класів, які мають метод 'stream': колекції, потоки введення, файли каталогів тощо. Але що робити, якщо ви хочете створити потік із користувацького циклу - скажімо, ітерація над курсором бази даних? Найкращий спосіб, який я знайшов поки що, - це створити ітератор, обернути його Spliterator і, нарешті, викликати StreamSupport # fromSpliterator. Занадто багато клею для простого корпусу IMHO. Є також Stream.iterate, але він виробляє нескінченний потік. Єдиний спосіб відрізати кашку в такому випадку - Stream # anyMatch, але це термінальна операція, тому ви не можете відокремити виробника та споживача потоку
Кирило Гамазков

2
RxJava має Observable.fromCallable, Observable.create тощо. Або ви можете сміливо виробляти нескінченне спостережливе, тоді скажіть ".прийміть, хоча (умова)", і ви все в порядку з доставкою цієї послідовності споживачам
Кирило Гамазков

1
Потоки не важко побудувати самостійно. Ви можете просто зателефонувати Stream.generate()та пройти власну Supplier<U>реалізацію, лише один простий метод, з якого ви надаєте наступний елемент у потоці. Існують навантаження інших методів. Щоб легко побудувати послідовність, Streamяка залежить від попередніх значень, ви можете використовувати interate()метод, кожен Collectionмає stream()метод і Stream.of()будує a Streamз varargs або масиву. Нарешті, StreamSupportє підтримка більш просунутого створення потоку за допомогою сплітераторів або для примітивних типів потоків.
jbx

"Потокам не вистачає операцій з відключенням ( takeWhile(), takeUntil());" - Я вважаю, що в JDK9 є takeWhile () і dropWhile ()
Abdul

50

Java 8 Stream і RxJava виглядає досить схоже. Вони мають схожих операторів (фільтр, карта, flatMap ...), але не створені для того ж використання.

Ви можете виконувати завдання асинхону за допомогою RxJava.

За допомогою потоку Java 8 ви будете переміщувати предмети колекції.

Ви можете зробити те саме, що і в RxJava (прохідні елементи колекції), але, оскільки RxJava зосереджена на одночасному завданні, ..., він використовує синхронізацію, засувку, ... Отже, те саме завдання, що використовує RxJava, може бути повільніше, ніж з потоком Java 8.

RxJava можна порівняти CompletableFuture, але це може обчислити більше ніж одне значення.


12
Варто відзначити, що твердження про обхід потоку справедливо лише для непаралельного потоку. parallelStreamпідтримує аналогічну синхронізацію простих обходів / карт / фільтрування тощо.
Джон Вінт

2
Я не думаю, "Тому те саме завдання з використанням RxJava може бути повільніше, ніж у потоці Java 8". справжній універсальний, він сильно залежить від завдання.
daschl

1
Я радий, що те саме завдання з використанням RxJava може бути повільніше, ніж у потоці Java 8 . Це дуже важлива відмінність, про яку багато користувачів RxJava не знають.
ІгорГанапольський

RxJava за замовчуванням синхронний. Чи є у вас якісь орієнтири для підтвердження вашої заяви про те, що це може бути повільніше?
Marcin Koziński

6
@ marcin-koziński ви можете перевірити цей орієнтир: twitter.com/akarnokd/status/752465265091309568
dwursteisen

37

Існує кілька технічних та концептуальних відмінностей, наприклад, потоки Java 8 - це одноразове використання, синхронна послідовність значень на основі витягу, тоді як спостережувані RxJava є повторно спостережуваними, адаптивно заснованими на натисканні, потенційно асинхронними послідовностями значень. RxJava орієнтований на Java 6+ та працює і на Android.


4
Типовий код, що включає RxJava, використовує лямбдаси, які доступні лише з Java 8 на. Таким чином, ви можете використовувати Rx з Java 6, але код буде шумно
Кирило Гамазков

1
Подібна відмінність полягає в тому, що Rx Observables може залишатися живим нескінченно, поки не підписаться. Потоки Java 8 закінчуються операціями за замовчуванням.
ІгорГанапольський

2
@KirillGamazkov ви можете використовувати retrolambda, щоб зробити свій код красивішим при націленні на Java 6.
Marcin Koziński

Котлін виглядає навіть сексуальніше, ніж модернізований
Кирило Гамазков

30

Потоки Java 8 базуються на основі потягу. Ви повторюєте потік Java 8, що споживає кожен елемент. І це може бути нескінченний потік.

RXJava Observableза замовчуванням заснований на push. Ви підписалися на спостережуване, і ви отримаєте сповіщення, коли наступний елемент прибуде ( onNext), або коли потік завершено ( onCompleted), або коли сталася помилка ( onError). Тому що з Observableви отримуєте onNext, onCompleted, onErrorподії, ви можете зробити деякі потужні функції , такі як об'єднання різних Observableсек на новий ( zip, merge, concat). Інші речі, які ви можете зробити, це кешування, дроселювання, ... І він використовує більш-менш один і той же API на різних мовах (RxJava, RX в C #, RxJS, ...)

За замовчуванням RxJava є однопотоковою. Якщо ви не почнете використовувати Планувальники, все відбуватиметься в одній темі.


в потоці у вас є Foreach, тобто в значній мірі те ж саме , чим onNext
PAUL

Насправді потоки зазвичай є термінальними. "Операції, що закривають потоковий трубопровід, називаються термінальними операціями. Вони отримують результат від трубопроводу, такого як Список, Цілий чи навіть недійсний (будь-який тип, який не є потоком)." ~ oracle.com/technetwork/articles/java/…
ІгорГанапольський

26

Існуючі відповіді є вичерпними та правильними, але чіткого прикладу для початківців не вистачає. Дозвольте мені поставити деякі конкретні терміни, такі як "push / pull-based" та "re-opable". Примітка : я ненавиджу цей термін Observable(це потік заради неба), тому просто посилаюся на потоки J8 проти RX.

Розглянемо список цілих чисел,

digits = [1,2,3,4,5]

Потік J8 - це утиліта для зміни колекції. Наприклад, навіть цифри можуть бути вилучені як,

evens = digits.stream().filter(x -> x%2).collect(Collectors.toList())

Це в основному карта Python , фільтрування, зменшення , дуже приємне (і давно прострочене) додаток до Java. Але що робити, якщо цифри не були зібрані достроково - а якщо цифри надходили в поточний час під час роботи програми - ми могли б відфільтрувати парні дані в реальному часі.

Уявіть, що окремий процес потоку виводить цілі числа у випадкові години, коли програма працює ( ---позначає час)

digits = 12345---6------7--8--9-10--------11--12

У RX evenможе реагувати на кожну нову цифру і застосовувати фільтр в режимі реального часу

even = -2-4-----6---------8----10------------12

Не потрібно зберігати вхідні та вихідні списки. Якщо ви хочете вивести список, не існує проблеми, яка також є поточною. Насправді все - це потік.

evens_stored = even.collect()  

Ось чому такі терміни, як "стан без громадянства" та "функціонал" більше асоціюються з RX


Але 5 навіть не є ... І це виглядає так, що J8 Stream синхронний, а Rx Stream - асинхронний?
Франклін Ю

1
@FranklinYu дякую, що я виправив 5 друкарських помилок. Якщо думати менше з точки зору синхронних проти асинхронних, хоча це може бути правильним, а більше з точки зору імперативу проти функціоналу. У J8 ви спочатку збираєте всі свої предмети, а потім застосовуєте фільтр другий. У RX ви визначаєте функцію фільтра незалежно від даних, а потім пов'язуєте її з рівним джерелом (прямим потоком або колекцією java) ... це зовсім інша модель програмування
Адам Хьюз

Я дуже здивований цим. Я впевнений, що потоки Java можуть створювати потокові дані. Що змушує вас думати навпаки?
Вік Седублеєв

4

RxJava також тісно пов'язаний з ініціативою реактивних потоків і розглядає його як просту реалізацію API реактивних потоків (наприклад, порівняно з реалізацією потоків Akka ). Основна відмінність полягає в тому, що реактивні потоки розроблені так, щоб вони могли справляти тиск проти, але якщо ви подивитеся на сторінку реактивних потоків, ви отримаєте ідею. Вони досить добре описують свої цілі, і потоки також тісно пов'язані з реактивним маніфестом .

Потоки Java 8 в значній мірі є реалізацією безмежної колекції, дуже схожою на Scala Stream або Clozyre Lezy seq .


3

Потоки Java 8 дозволяють ефективно обробляти дійсно великі колекції, використовуючи багатоядерні архітектури. На відміну від цього, RxJava є однопоточним за замовчуванням (без Планувальників). Тож RxJava не скористається багатоядерними машинами, якщо ви самі не кодуєте цю логіку.


4
Потік також є однопоточним за замовчуванням, якщо ви не викликаєте .parallel (). Також Rx дає більше контролю над одночасністю.
Кирило Гамазков

@KirillGamazkov Котлін Coroutines Flow (заснований на потоках Java8) тепер підтримує структуровану сумісність
IgorGanapolsky

Щоправда, але я нічого не говорив про Flow та структуровану одночасність. Мої два моменти були: 1) і Stream, і Rx є однопотоковими, якщо ви явно не зміните це; 2) Rx дає точний контроль над тим, який крок виконувати на якому пулі потоків, на відміну від потоків, що лише дозволяють сказати "зробіть це паралельно якось"
Кирило Гамазков

Я не дуже розумію питання "для чого вам потрібен пул потоків". Як ви вже говорили, "для ефективної обробки дійсно великих колекцій". А може, я хочу, щоб частина завдань, пов'язаних з IO, виконувалася в окремому пулі потоків. Я не думаю, що я зрозумів наміри вашого питання. Спробуйте ще раз?
Кирило Гамазков

1
Статичні методи в класі Schedulers дозволяють отримувати заздалегідь задані пули потоків, а також створювати їх з Executor. Дивіться reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/…
Кирило Гамазков
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.