Họ đang đo gì
Bạn dùng stream vì nó đọc dễ hơn, hay vì bạn tin nó nhanh hơn vòng lặp.
Trả lời ngắn~30 giây
Các thao tác trung gian (map, filter) chỉ dựng đường ống; không có gì chạy cho tới khi gặp thao tác kết thúc (collect, forEach, count). Nhờ vậy stream hợp nhất các bước và có thể dừng sớm — findFirst không duyệt hết. parallelStream() chạy trên common ForkJoinPool DÙNG CHUNG toàn JVM, nên một tác vụ chặn ở đó làm nghẽn mọi phần khác của ứng dụng; và với danh sách nhỏ thì chi phí chia việc lớn hơn phần tiết kiệm được.
Giải thích sâu
Điểm dễ bỏ sót về lười biếng: nó thay đổi SỐ LẦN hàm của bạn được gọi. Với list.stream().map(this::expensive).filter(x -> x > 0).findFirst(), expensive chỉ chạy cho tới khi tìm được phần tử đầu tiên thoả — không phải cho cả danh sách. Viết bằng hai vòng lặp riêng thì bạn tính hết rồi mới lọc. Đây là lợi ích thật của stream, và nó không liên quan gì tới song song.
Về parallelStream(), vấn đề lớn hơn hiệu năng là chia sẻ tài nguyên. Common pool có số luồng bằng số lõi trừ một, và MỌI parallelStream() trong tiến trình đều dùng chung nó. Một chỗ trong code gọi API bên ngoài bên trong parallel stream sẽ chiếm luồng suốt thời gian chờ mạng, và các parallel stream khác — có thể ở tầng hoàn toàn khác — đứng chờ. Nếu thật sự cần song song, hãy nộp việc vào ForkJoinPool riêng của bạn.
Ngoài ra parallelStream() chỉ thắng khi ba điều kiện cùng đúng: dữ liệu chia đôi rẻ (mảng, ArrayList — chứ không phải LinkedList), công việc mỗi phần tử đủ nặng, và không có trạng thái chia sẻ. Trong dịch vụ web, điều kiện thứ tư còn quan trọng hơn: bạn ĐÃ song song ở mức request rồi, nên song song thêm bên trong chỉ tranh CPU với chính các request khác.
Câu hỏi tiếp theo họ sẽ hỏi
?Collectors.toMap có bẫy gì?
Nó ném IllegalStateException khi gặp khoá trùng, thay vì ghi đè im lặng như bạn có thể mong đợi. Phải truyền hàm gộp làm tham số thứ ba. Nó cũng ném NPE nếu giá trị là null, khác với HashMap thông thường.
?Stream có tái sử dụng được không?
Không. Một stream chỉ tiêu thụ được một lần; gọi thao tác kết thúc lần hai ném IllegalStateException. Muốn duyệt lại thì tạo stream mới từ nguồn, hoặc Supplier<Stream<T>> nếu bạn cần truyền đi.
Trả lời thế này là mất điểm
- Dùng
parallelStream()như một cách tăng tốc mặc định. Trong ứng dụng web, đây gần như luôn làm p99 tệ hơn. - Sửa biến bên ngoài trong
forEach. Nó phá tính đúng khi song song và làm mất luôn khả năng suy luận về đường ống.