2015-07-01 2 views
9

Я использую разделитель потоков непосредственно для операций низкого уровня в библиотеке, которую я пишу. Недавно я обнаружил очень странное поведение, когда я принимаю разделитель потока и чередую вызовы tryAdvance/trySplit. Вот простой код, который демонстрирует проблему:Странное поведение Stream.spliterator для параллельных потоков

import java.util.Arrays; 
import java.util.Spliterator; 

public class SpliteratorBug { 
    public static void main(String[] args) { 
     Integer[][] input = { { 1 }, { 2, 3 }, { 4, 5, 6 }, { 7, 8 }, { 9 } }; 
     Spliterator<Integer> spliterator = Arrays.stream(input).parallel() 
       .flatMap(Arrays::stream).spliterator(); 
     spliterator.trySplit(); 
     spliterator.tryAdvance(s -> {}); 
     spliterator.trySplit(); 
     spliterator.forEachRemaining(System.out::println); 
    } 
} 

Выход

5 
6 
9 

Как вы можете видеть, после плоского картирования я должен получить заказанный поток последовательных чисел от 1 до 9. Я разбил разделитель один раз, поэтому он должен перейти в какое-то промежуточное положение. Затем я использую элемент из него и разбиваю его еще раз. После этого я распечатываю все остальные элементы. Я ожидаю, что у меня будет несколько последовательных элементов из хвоста потока (возможно, нулевые элементы, это тоже будет хорошо). Однако я получаю 5 и 6, затем внезапный прыжок до 9.

Я знаю, что в настоящее время в разбрасывателях JDK не используются так: они всегда разделяются до обхода. Однако официальный documentation не запрещает прямое обращение к trySplit после tryAdvance.

Проблема не наблюдалась, когда я использую spliterator, созданный непосредственно из коллекции, массива, сгенерированного источника и т. Д. Это наблюдается только в том случае, если spliterator был создан из параллельного потока, который имел промежуточный flatMap.

Итак, вопрос: я попал в ошибку или явным образом запретил где-то использовать spliterator таким образом?

ответ

3

Из того, что я могу видеть из источника AbstractWrappingSpliterator и компании, когда вы tryAdvance, выход flatMap (4,5,6) получает буфер и затем 4 получает выгорело, оставив (5,6) в буфере. Затем trySplit правильно расщепляет (7,8) на новый Spliterator, оставляя 9 в старом, но буферизованный (5,6) остается со старым Spliterator.

Так что это выглядит как ошибка для меня. Он должен либо отложить буфер до нового Spliterator, либо вернуть null и отказаться от разделения, если буфер не пуст.

2

Из документации Spliterator.trySplit():

Этот метод может возвращать null по любой причине, в том числе пустоты, неспособность разделить после обхода началось, ограничения структуры данных, а также соображения эффективности.

(курсив мой)

Так документация явно упоминает возможность попытаться расщеплению после начала обхода и предполагает, что spliterators, которые не могут справиться с этим может вернуться null.

Так что для упорядоченных разделителей наблюдаемое поведение должно считаться ошибкой as described by Misha.Как правило, тот факт, что trySplit() должен вернуть префикс splitterator, другими словами, должен передать все промежуточное состояние относительно следующих элементов новому разделителю, является особенностью API Spliterator, что делает ошибки вероятными. Я взял этот вопрос в качестве мотива для проверки своих собственных реализаций spliterator и обнаружил подобную ошибку ...

1

Такое поведение было официально признано как ошибка (см JDK-8148838), закрепленное на меня и толкнул в багажник JDK-9 (см changeset) , Печально то, что мой первоначальный патч фактически зафиксировал расщепление после flatMap (см. webrev), но этот патч был отклонен, так как такой сценарий (с использованием trySplit() после tryAdvance()) считался необычным и обескураженным. В настоящее время принято решение отключить разделение WrappingSpliterator после перехода на все, что достаточно для устранения проблемы.

Смежные вопросы