2015-03-11 12 views
11

Mam kolekcję elementów, które chcę przetwarzać równolegle. Kiedy używam List, działa równoległość. Jednak kiedy używam Set, nie działa on równolegle.Strumień równoległy z HashSet nie działa równolegle

pisałem próbkę kodu, który pokazuje problem:

public static void main(String[] args) { 
    ParallelTest test = new ParallelTest(); 

    List<Integer> list = Arrays.asList(1,2); 
    Set<Integer> set = new HashSet<>(list); 

    ForkJoinPool forkJoinPool = new ForkJoinPool(4); 

    System.out.println("set print"); 
    try { 
     forkJoinPool.submit(() -> 
      set.parallelStream().forEach(test::print) 
     ).get(); 
    } catch (Exception e) { 
     return; 
    } 

    System.out.println("\n\nlist print"); 
    try { 
     forkJoinPool.submit(() -> 
      list.parallelStream().forEach(test::print) 
     ).get(); 
    } catch (Exception e) { 
     return; 
    } 
} 

private void print(int i){ 
    System.out.println("start: " + i); 
    try { 
     TimeUnit.SECONDS.sleep(1); 
    } catch (InterruptedException e) { 
    } 
    System.out.println("end: " + i); 
} 

Jest to wyjście, które dostaję na Windows 7

set print 
start: 1 
end: 1 
start: 2 
end: 2 

list print 
start: 2 
start: 1 
end: 1 
end: 2 

Widzimy, że pierwszy element z Set musiał zakończyć przed przetworzeniem drugiego elementu. W przypadku modelu List drugi element rozpoczyna się przed zakończeniem pierwszego elementu.

Czy możesz mi powiedzieć, co powoduje ten problem i jak tego uniknąć, używając kolekcji Set?

+0

spróbuj z więcej niż dwoma elementami, takimi jak 10. elementy lub coś takiego.wyniki z 2 są zbyt nieprecyzyjne. – nafas

+0

Przy próbie z 10 nadal nie można połączyć wszystkich ustawionych elementów. I muszę uruchomić wszystkie elementy równolegle. – Nemo

+0

Każdy sposób jest to wyjście do 10 (z puli 10 wykonawców) elementy wymienione drukowania początek 8 początek 0 start: 4 początkową 6 początek 2 koniec 2 koniec 6 koniec: 4 koniec 0 start: 1 koniec 8 start: 9 początkowa: 5 początek 7 start: 3 zakończenie: 3 koniec: 5 koniec: 9 koniec 7 koniec 1 lista druku start: 7 start: 3 początek 0 początkową 6 start: 9 początek 8 początkowa: 5 start: 4 początek 2 start: 1 koniec 0 koniec: 6 koniec 7 koniec: 9 koniec 2 zakończenie: 3 koniec 8 koniec: 5 koniec 1 zakończenie: 4 Nie wszystkie zestaw elementów prowadzony równolegle – Nemo

Odpowiedz

20

Mogę odtworzyć zachowanie, które widzisz, gdzie równoległość nie pasuje do paralelności podanego przez ciebie paralelizmu łączenia wideł. Po ustawieniu równoległości parowania widełek na 10 i zwiększeniu liczby elementów w kolekcji do 50, widzę paralelność strumienia opartego na listach rosnącego tylko do 6, podczas gdy paralelność strumienia opartego na zestawie nigdy nie wznosi się powyżej 2.

Należy jednak zauważyć, że ta technika przesyłania zadania do puli łączenia widełek w celu uruchomienia strumienia równoległego w tej puli jest "sztuczką" związaną z implementacją i nie jest gwarantowanado pracy. Rzeczywiście, wątki lub pule wątków używane do wykonywania równoległych strumieni to nieokreślony. Domyślnie używana jest wspólna pula łączenia widmowego, ale w różnych środowiskach mogą być używane różne pule wątków. (Rozważ kontener na serwerze aplikacji.)

W klasie java.util.stream.AbstractTask pole LEAF_TARGET określa ilość dzielenia, która jest wykonywana, co z kolei określa ilość równoległości, jaka może zostać osiągnięta. Wartość tego pola jest oparta na ForkJoinPool.getCommonPoolParallelism(), która oczywiście wykorzystuje paralelizm wspólnej puli, a żadna pula nie wykonuje zadań.

Prawdopodobnie jest to błąd (patrz wydanie OpenJDK JDK-8190974), jednak ten cały obszar jest nieokreślony. Jednak ten obszar systemu zdecydowanie wymaga rozwoju, na przykład w zakresie polityki dzielenia, ilości dostępnych równoległości, zajmujących się między innymi blokowaniem zadań. Przyszłe wydanie JDK może rozwiązać niektóre z tych problemów.

W międzyczasie można kontrolować równoległość wspólnej puli łączenia widełek za pomocą właściwości systemu. Jeśli dodać tę linię do swojego programu,

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "10"); 

i uruchomieniu strumienie we wspólnej puli (lub jeśli przedkłada je do własnego basenu, który ma wystarczająco wysoki poziom zestawu równoległości) można zauważyć, że wiele więcej zadań uruchamianych jest równolegle.

Możesz także ustawić tę właściwość w wierszu poleceń, używając opcji -D.

To zachowanie nie jest gwarantowane i może się zmienić w przyszłości. Ale ta technika prawdopodobnie będzie działać w implementacjach JDK 8 w przewidywalnej przyszłości.

+0

Pochodzący z [tutaj] (http: //stackoverflow.com/questions/36947336/why-does-the-parallel-strea m-not-use-all-the-threads-of-the-forkjoinpool? noredirect = 1), pytanie dotyczące _Notwierdziłabym, że jest to błąd_: czy poprawka miałaby używać paralelizmu bieżącego 'ForkJoinPool' (domyślnie lub w inny sposób) albo coś innego? –

+0

Nie, nie błąd. W ten sposób działa 'ForkJoinPool' - ma mechanizm podobny do przeciwciśnienia, który zapobiega niepotrzebnemu rozprzestrzenianiu się nici. Dodałem odpowiedź na pytanie, które łączysz, wyjaśniając to zachowanie. –

+1

@SotiriosDelimanolis Zobacz także komentarz Dimitar tutaj. Widzę, że dyskutujecie o tym także na [inne pytanie] (http://stackoverflow.com/questions/36947336/why-does--równolegle--nie-nie-użytkowniku-w-przeniesieniu-- forkjoinpool) –

Powiązane problemy