Sự khác biệt giữa các luồng Java 8 và các đài quan sát RxJava


144

Các luồng Java 8 có giống với các quan sát RxJava không?

Định nghĩa luồng Java 8:

Các lớp trong java.util.streamgói mới cung cấp API Stream để hỗ trợ các hoạt động theo kiểu chức năng trên các luồng phần tử.


8
FYI có đề xuất giới thiệu thêm RxJava như các lớp trong JDK 9. jsr166-concurrency.10961.n7.nabble.com/ The
John Vint

@JohnVint Tình trạng của đề xuất này là gì. Nó sẽ thực sự đi máy bay?
IgorGanapolsky

2
@IgorGanapolsky Ồ vâng, có vẻ như nó sẽ biến nó thành jdk9. cr.openjdk.java.net/~martin/webrevs/openjdk9/ . Thậm chí còn có một cổng cho RxJava để Flow github.com/akarnokd/RxJavaUtilConcienFlow .
John Vint

Tôi biết đây là một câu hỏi thực sự cũ, nhưng gần đây tôi đã tham dự buổi nói chuyện tuyệt vời này của Venkat Subramaniam, người có cái nhìn sâu sắc về chủ đề này và được cập nhật lên Java9: youtube.com/watch?v=kfSSKM9y_0E . Có thể thú vị cho những người đào sâu vào RxJava.
Pedro

Câu trả lời:


152

TL; DR : Tất cả các lib xử lý chuỗi / luồng đang cung cấp API rất giống nhau để xây dựng đường ống. Sự khác biệt là trong API để xử lý đa luồng và thành phần của đường ống.

RxJava khá khác so với Stream. Trong tất cả mọi thứ của JDK, gần nhất với rx.Observable có lẽ là java.util.stream.Collector Stream + CompleteableFuture (có chi phí xử lý lớp đơn lớp bổ sung, tức là phải xử lý chuyển đổi giữa Stream<CompletableFuture<T>>CompletableFuture<Stream<T>>).

Có sự khác biệt đáng kể giữa Quan sát và Luồng:

  • Luồng là dựa trên kéo, quan sát là dựa trên đẩy. Điều này nghe có vẻ quá trừu tượng, nhưng nó có những hậu quả đáng kể rất cụ thể.
  • Luồng chỉ có thể được sử dụng một lần, Có thể đăng ký nhiều lần
  • Stream#parallel()chia chuỗi thành các phân vùng, Observable#subscribeOn()Observable#observeOn()không; Thật khó để mô phỏng Stream#parallel()hành vi với Observable, nó đã từng có .parallel()phương thức nhưng phương thức này gây ra nhiều nhầm lẫn đến mức .parallel()hỗ trợ đã được chuyển sang kho lưu trữ riêng trên github, RxJavaParallel. Thêm chi tiết trong một câu trả lời khác .
  • Stream#parallel()không cho phép chỉ định nhóm luồng sử dụng, không giống như hầu hết các phương thức RxJava chấp nhận Bộ lập lịch tùy chọn. Do tất cả các phiên bản luồng trong JVM sử dụng cùng một nhóm kết nối ngã ba, việc thêm .parallel()có thể vô tình ảnh hưởng đến hành vi trong một mô-đun khác của chương trình của bạn
  • Các luồng đang thiếu các hoạt động liên quan đến thời gian như Observable#interval(), Observable#window()và nhiều hoạt động khác; điều này chủ yếu là do Luồng dựa trên kéo và ngược dòng không kiểm soát được khi nào phát ra phần tử tiếp theo xuôi dòng
  • Các luồng cung cấp tập hợp các hoạt động bị hạn chế so với RxJava. Ví dụ: Luồng đang thiếu các hoạt động cắt ( takeWhile(), takeUntil()); cách sử dụng thay thế Stream#anyMatch()bị hạn chế: đó là hoạt động của thiết bị đầu cuối, vì vậy bạn không thể sử dụng nhiều lần trên mỗi luồng
  • Kể từ JDK 8, đôi khi không có hoạt động Stream # zip, đôi khi khá hữu ích
  • Luồng khó tự xây dựng, Có thể quan sát được bằng nhiều cách EDIT: Như đã lưu ý trong các nhận xét, có nhiều cách để tạo Luồng. Tuy nhiên, do không có đoản mạch không đầu cuối, nên bạn không thể dễ dàng tạo Luồng dòng trong tệp (JDK cung cấp các dòng Tệp # và BufferedReader # ngoài hộp và các kịch bản tương tự khác có thể được quản lý bằng cách xây dựng Luồng từ Iterator).
  • Có thể quan sát cung cấp cơ sở quản lý tài nguyên ( Observable#using()); bạn có thể gói luồng IO hoặc mutex với nó và chắc chắn rằng người dùng sẽ không quên giải phóng tài nguyên - nó sẽ được xử lý tự động khi chấm dứt đăng ký; Stream có onClose(Runnable)phương thức, nhưng bạn phải gọi nó bằng tay hoặc thông qua try-with-resource. Ví dụ. bạn phải nhớ rằng Tập tin # dòng () phải được đặt trong khối tài nguyên thử.
  • Các đài quan sát được đồng bộ hóa suốt (Tôi thực sự không kiểm tra xem điều đó có đúng với Luồng không). Điều này khiến bạn không nghĩ liệu các hoạt động cơ bản có an toàn cho luồng hay không (câu trả lời luôn là 'có', trừ khi có lỗi), nhưng chi phí liên quan đến đồng thời sẽ ở đó, bất kể mã của bạn có cần hay không.

Làm tròn: RxJava khác với Luồng đáng kể. Các lựa chọn thay thế RxJava thực là các triển khai khác của ReactiveStreams , ví dụ như phần có liên quan của Akka.

Cập nhật . Có mẹo sử dụng nhóm kết nối ngã ba không mặc định cho Stream#parallel, xem Nhóm luồng tùy chỉnh trong luồng song song Java 8

Cập nhật . Tất cả những điều trên được dựa trên trải nghiệm với RxJava 1.x. Bây giờ RxJava 2.x đã ở đây , câu trả lời này có thể đã lỗi thời.


2
Tại sao các luồng khó xây dựng? Theo bài viết này, có vẻ dễ dàng: oracle.com/technetwork/articles/java/
Kẻ

2
Có khá nhiều lớp có phương thức 'stream': bộ sưu tập, luồng đầu vào, tệp thư mục, v.v. Nhưng nếu bạn muốn tạo một luồng từ một vòng lặp tùy chỉnh - giả sử, lặp qua con trỏ cơ sở dữ liệu thì sao? Cách tốt nhất mà tôi đã tìm thấy cho đến nay là tạo Iterator, bọc nó bằng Spliterator và cuối cùng gọi StreamSupport # fromSpliterator. Quá nhiều keo cho một trường hợp đơn giản IMHO. Ngoài ra còn có Stream.iterate nhưng nó tạo ra luồng vô hạn. Cách duy nhất để loại bỏ tiếng hét trong trường hợp đó là Stream # anyMatch, nhưng đó là một hoạt động đầu cuối, do đó bạn không thể tách rời nhà sản xuất và người tiêu dùng luồng
Kirill Gamazkov

2
RxJava có Observable.fromCallable, Observable.create, v.v. Hoặc bạn có thể sản xuất có thể quan sát vô hạn một cách an toàn, sau đó nói '.takeWhile (điều kiện)', và bạn vẫn ổn khi vận chuyển chuỗi này đến người tiêu dùng
Kirill Gamazkov

1
Luồng không khó để tự xây dựng. Bạn có thể chỉ cần gọi Stream.generate()và vượt qua Supplier<U>triển khai của riêng bạn , chỉ một phương thức đơn giản mà bạn cung cấp mục tiếp theo trong luồng. Có vô số phương pháp khác. Để dễ dàng xây dựng một chuỗi Streamphụ thuộc vào các giá trị trước đó, bạn có thể sử dụng interate()phương thức, mỗi phương thức Collectioncó một stream()phương thức và Stream.of()xây dựng một chuỗi Streamtừ một varargs hoặc mảng. Cuối cùng StreamSupportcó hỗ trợ cho việc tạo luồng nâng cao hơn bằng cách sử dụng bộ chia hoặc cho các kiểu nguyên thủy của luồng.
jbx

"Luồng đang thiếu các hoạt động cắt ( takeWhile(), takeUntil());" - JDK9 có những thứ này, tôi tin rằng, trong TakeWhile ()dropWhile ()
Abdul

50

Java 8 Stream và RxJava trông khá giống nhau. Chúng có các toán tử giống nhau (bộ lọc, bản đồ, bản đồ phẳng ...) nhưng không được xây dựng cho cùng một cách sử dụng.

Bạn có thể thực hiện các tác vụ asynchonus bằng RxJava.

Với luồng Java 8, bạn sẽ duyệt qua các mục trong bộ sưu tập của mình.

Bạn có thể thực hiện khá nhiều điều tương tự trong RxJava (các mục ngang của bộ sưu tập), nhưng, vì RxJava tập trung vào nhiệm vụ đồng thời, ..., nó sử dụng đồng bộ hóa, chốt, ... Vì vậy, cùng một tác vụ sử dụng RxJava có thể chậm hơn với luồng Java 8.

RxJava có thể được so sánh với CompletableFuture, nhưng điều đó có thể tính toán nhiều hơn chỉ một giá trị.


12
Đáng lưu ý rằng bạn tuyên bố về truyền tải luồng chỉ đúng với luồng không song song. parallelStreamhỗ trợ đồng bộ hóa tương tự các đường ngang / bản đồ / lọc đơn giản, v.v.
John Vint

2
Tôi không nghĩ "Vì vậy, cùng một tác vụ sử dụng RxJava có thể chậm hơn so với luồng Java 8." giữ đúng phổ quát, nó phụ thuộc rất nhiều vào nhiệm vụ trong tay.
daschl

1
Tôi rất vui vì bạn đã nói rằng tác vụ tương tự khi sử dụng RxJava có thể chậm hơn so với luồng Java 8 . Đây là một điểm khác biệt rất quan trọng mà nhiều người dùng RxJava không biết đến.
IgorGanapolsky

RxJava được đồng bộ theo mặc định. Bạn có bất kỳ điểm chuẩn nào để hỗ trợ cho tuyên bố của mình rằng nó có thể chậm hơn không?
Marcin Koziński

6
@ marcin-koziński bạn có thể kiểm tra điểm chuẩn này: twitter.com/akarnokd/status/752465265091309568
dwursteisen

37

Có một số khác biệt về kỹ thuật và mang thai, ví dụ, các luồng Java 8 là các chuỗi giá trị được sử dụng một lần, dựa trên kéo, trong khi RxJava Observables có thể quan sát được, dựa trên kéo thích ứng, các chuỗi có giá trị không đồng bộ. RxJava nhắm đến Java 6+ và cũng hoạt động trên Android.


4
Mã điển hình liên quan đến RxJava sử dụng rất nhiều lambdas chỉ có sẵn từ Java 8 trở đi. Vì vậy, bạn có thể sử dụng Rx với Java 6, nhưng mã sẽ ồn ào
Kirill Gamazkov

1
Một điểm khác biệt tương tự là Rx Observables có thể tồn tại vô thời hạn cho đến khi không được đăng ký. Các luồng Java 8 được kết thúc bằng các hoạt động theo mặc định.
IgorGanapolsky

2
@KirillGamazkov bạn có thể sử dụng retrolambda để làm cho mã của bạn đẹp hơn khi nhắm mục tiêu Java 6.
Marcin Koziński

Kotlin trông còn quyến rũ hơn cả trang bị thêm
Kirill Gamazkov

30

Luồng Java 8 dựa trên kéo. Bạn lặp lại một luồng Java 8 tiêu thụ từng mục. Và nó có thể là một dòng vô tận.

RXJava Observabletheo mặc định dựa trên đẩy. Bạn đăng ký một Đài quan sát và bạn sẽ nhận được thông báo khi mục tiếp theo đến ( onNext) hoặc khi luồng hoàn thành ( onCompleted) hoặc khi xảy ra lỗi ( onError). Bởi vì với Observablebạn nhận được onNext, onCompleted, onErrorsự kiện, bạn có thể làm một số chức năng mạnh mẽ như cách kết hợp khác nhau Observables để một hình mới ( zip, merge, concat). Những thứ khác bạn có thể làm là lưu trữ bộ đệm, điều chỉnh, ... Và nó sử dụng ít nhiều cùng một API trong các ngôn ngữ khác nhau (RxJava, RX bằng C #, RxJS, ...)

Theo mặc định RxJava là luồng đơn. Trừ khi bạn bắt đầu sử dụng Trình lập lịch biểu, mọi thứ sẽ diễn ra trên cùng một chuỗi.


trong Stream bạn có forEach, nó khá giống với onNext
paul

Trên thực tế, Stream thường là thiết bị đầu cuối. "Các hoạt động đóng một đường ống luồng được gọi là các hoạt động đầu cuối. Chúng tạo ra kết quả từ một đường ống như Danh sách, Số nguyên hoặc thậm chí vô hiệu (bất kỳ loại không phải luồng nào)." ~ oracle.com/technetwork/articles/java/
Mạnh

26

Các câu trả lời hiện có là toàn diện và chính xác, nhưng một ví dụ rõ ràng cho người mới bắt đầu là thiếu. Cho phép tôi đặt một số cụ thể đằng sau các thuật ngữ như "đẩy / kéo dựa" và "có thể quan sát lại". Lưu ý : Tôi ghét thuật ngữ này Observable(đó là một luồng vì lợi ích của thiên đường), vì vậy sẽ chỉ đơn giản đề cập đến các luồng J8 vs RX.

Hãy xem xét một danh sách các số nguyên,

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

Luồng J8 là một tiện ích để sửa đổi bộ sưu tập. Ví dụ, các chữ số chẵn có thể được trích xuất là,

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

Về cơ bản, đây là bản đồ, bộ lọc, thu nhỏ của Python , một bổ sung rất hay (và quá hạn dài) cho Java. Nhưng điều gì sẽ xảy ra nếu các chữ số không được thu thập trước thời hạn - điều gì sẽ xảy ra nếu các chữ số đang phát trực tuyến trong khi ứng dụng đang chạy - chúng ta có thể lọc các số chẵn trong thời gian thực không.

Hãy tưởng tượng một quy trình xử lý luồng riêng biệt xuất ra số nguyên vào các thời điểm ngẫu nhiên trong khi ứng dụng đang chạy ( ---biểu thị thời gian)

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

Trong RX, evencó thể phản ứng với từng chữ số mới và áp dụng bộ lọc trong thời gian thực

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

Không cần lưu trữ danh sách đầu vào và đầu ra. Nếu bạn muốn có một danh sách đầu ra, không có vấn đề gì có thể phát trực tuyến. Trong thực tế, tất cả mọi thứ là một luồng.

evens_stored = even.collect()  

Đây là lý do tại sao các thuật ngữ như "không trạng thái" và "chức năng" được liên kết nhiều hơn với RX


Nhưng 5 thậm chí không phải là bản sao Và có vẻ như J8 Stream là đồng bộ, trong khi Rx Stream không đồng bộ?
Franklin Yu

1
@FranklinYu cảm ơn tôi đã sửa 5 lỗi đánh máy. Nếu suy nghĩ ít hơn về mặt đồng bộ so với asyncrhouns, mặc dù nó có thể đúng và nhiều hơn về mặt mệnh lệnh so với chức năng. Trong J8, bạn thu thập tất cả các mục của mình trước, sau đó áp dụng bộ lọc thứ hai. Trong RX, bạn xác định chức năng lọc độc lập với dữ liệu và sau đó liên kết nó với nguồn chẵn (luồng trực tiếp hoặc bộ sưu tập java) ... đó là một mô hình lập trình hoàn toàn khác
Adam Hughes

Tôi rất ngạc nhiên về điều này. Tôi khá chắc chắn rằng các luồng Java có thể được tạo từ luồng dữ liệu. Điều gì khiến bạn nghĩ ngược lại?
Vic Seedoubleyew

4

RxJava cũng liên quan chặt chẽ với sáng kiến ​​luồng phản ứng và tự coi đó là một triển khai đơn giản của API luồng phản ứng (ví dụ so với triển khai luồng Akka ). Sự khác biệt chính là, các luồng phản ứng được thiết kế để có thể xử lý áp lực ngược, nhưng nếu bạn xem trang luồng phản ứng, bạn sẽ có ý tưởng. Họ mô tả các mục tiêu của họ khá tốt và các luồng cũng liên quan chặt chẽ đến tuyên ngôn phản ứng .

Các luồng Java 8 khá nhiều khi thực hiện một bộ sưu tập không giới hạn, khá giống với Scala Stream hoặc Clojure lười biếng seq .


3

Luồng Java 8 cho phép xử lý các bộ sưu tập thực sự lớn một cách hiệu quả, trong khi tận dụng các kiến ​​trúc đa lõi. Ngược lại, RxJava được phân luồng đơn theo mặc định (không có Trình lập lịch biểu). Vì vậy, RxJava sẽ không tận dụng các máy đa lõi trừ khi bạn tự viết mã logic đó.


4
Luồng được phân luồng đơn theo mặc định là tốt, trừ khi bạn gọi. Vô tuyến (). Ngoài ra, Rx cung cấp thêm quyền kiểm soát đồng thời.
Kirill Gamazkov

@KirillGamazkov Dòng chảy của Corlinines Kotlin (dựa trên Luồng Java8) hiện hỗ trợ đồng thời có cấu trúc: kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky

Đúng, nhưng tôi không nói gì về Flow và cấu trúc đồng thời. Hai điểm của tôi là: 1) cả Stream và Rx đều là luồng đơn trừ khi bạn thay đổi rõ ràng điều đó; 2) Rx cung cấp cho bạn quyền kiểm soát chi tiết về bước nào sẽ thực hiện trên nhóm luồng nào, ngược lại với Luồng chỉ cho phép bạn nói "làm cho nó song song bằng cách nào đó"
Kirill Gamazkov 15/12/19

Tôi thực sự không hiểu được câu hỏi "bạn cần nhóm chủ đề để làm gì". Như bạn đã nói, "để cho phép xử lý các bộ sưu tập thực sự lớn một cách hiệu quả". Hoặc có lẽ tôi muốn một phần của nhiệm vụ ràng buộc IO chạy trên nhóm luồng riêng biệt. Tôi không nghĩ rằng tôi đã hiểu ý định đằng sau câu hỏi của bạn. Thử lại?
Kirill Gamazkov

1
Các phương thức tĩnh trong lớp Lập lịch cho phép nhận các nhóm luồng được xác định trước cũng như tạo một nhóm từ Executor. Xem reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/...
Kirill Gamazkov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.