0Pricing
Java Academy · Lekcja

Typowe pułapki

Kolejność, boxing i wspólna pula

Typowe pułapki to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Równoległość nie daje darmowego przyspieszenia

Dodanie parallel() może spowolnić kod zamiast go przyspieszyć. W tej lekcji omówiono typowe pułapki, aby można było ich uniknąć.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long sum = IntStream.rangeClosed(1, 1_000_000).parallel()
            .asLongStream().sum();
        System.out.println(sum);
    }
}

Pułapka: narzut związany z kolejnością

Operacje zachowujące kolejność, takie jak forEachOrdered lub limit, wymuszają koordynację między wątkami, zmniejszając korzyści z równoległości.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.rangeClosed(1, 5).parallel()
            .forEachOrdered(System.out::println);
    }
}

unordered może pomóc

Jeśli kolejność nie ma znaczenia, wywołanie unordered() pozwala środowisku uruchomieniowemu pominąć operacje związane z kolejnością i swobodniej wykonywać pracę równolegle.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long count = IntStream.rangeClosed(1, 1_000_000).parallel()
            .unordered()
            .filter(n -> n % 5 == 0)
            .count();
        System.out.println(count);
    }
}

Pułapka: ukryty boxing

Używanie Stream<Integer> równolegle zwiększa koszt boxingu w wielu wątkach. W przypadku równoległych operacji numerycznych należy preferować strumienie typów prostych.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long sum = IntStream.rangeClosed(1, 1_000_000).parallel().asLongStream().sum();
        System.out.println(sum);
    }
}

Pułapka: współdzielona pula common

Wszystkie strumienie równoległe współdzielą jedną pulę ForkJoinPool.commonPool(). Długotrwałe zadanie równoległe może zagłodzić inne zadania w tej samej maszynie JVM, w tym inne części aplikacji.

import java.util.concurrent.ForkJoinPool;

public class Main {
    public static void main(String[] args) {
        System.out.println(ForkJoinPool.commonPool().getParallelism());
    }
}

Izolowanie pracy we własnej puli

Aby nie obciążać wspólnej puli ciężkimi zadaniami, należy uruchamiać strumień równoległy we własnej puli ForkJoinPool.

import java.util.concurrent.ForkJoinPool;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) throws Exception {
        ForkJoinPool pool = new ForkJoinPool(2);
        long sum = pool.submit(() ->
            IntStream.rangeClosed(1, 1_000_000).parallel().asLongStream().sum()
        ).get();
        pool.shutdown();
        System.out.println(sum);
    }
}

Pułapka: źródła trudne do podziału

LinkedList, Stream.iterate i BufferedReader.lines dzielą się słabo. Ich równoległe wykonywanie często daje niewielką korzyść lub wręcz pogarsza wydajność.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        long n = Stream.iterate(1, x -> x + 1)
            .limit(100_000)
            .parallel()
            .filter(x -> x % 2 == 0)
            .count();
        System.out.println(n);
    }
}

Pułapka: stanowe lambdy

Lambdy, które odczytują lub modyfikują zewnętrzny stan, działają niedeterministycznie w trybie równoległym. Aby uzyskać spójne wyniki, należy zachować ich bezstanowość.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 100).parallel()
            .map(n -> n + 1)
            .sum();
        System.out.println(sum);
    }
}

Pułapka: findFirst a findAny

findFirst musi zachować kolejność, co powoduje dodatkowy narzut w trybie równoległym. Jeśli wystarczy dowolny pasujący element, należy użyć findAny.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int found = IntStream.rangeClosed(1, 1_000_000).parallel()
            .filter(n -> n % 99991 == 0)
            .findAny()
            .getAsInt();
        System.out.println(found);
    }
}

Pułapka: tanie operacje na małych zbiorach danych

W przypadku bardzo małych kolekcji lub trywialnych operacji narzut dzielenia i scalania przewyższa wszelkie korzyści. Należy pozostać przy trybie sekwencyjnym.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> small = List.of(1, 2, 3, 4);
        int sum = small.stream().mapToInt(Integer::intValue).sum();
        System.out.println(sum);
    }
}

Pułapka: kosztowne scalanie w kolektorach

Kolektory z kosztownym etapem scalania, takie jak te budujące duże posortowane struktury, mogą zniwelować korzyści z równoległości. Proste operacje gromadzenia można scalać tanio.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long total = IntStream.rangeClosed(1, 1_000_000).parallel()
            .mapToLong(n -> n)
            .sum();
        System.out.println(total);
    }
}

Szybkie sprawdzenie

Dlaczego findFirst może działać wolniej niż findAny na strumieniu równoległym?

Podsumowanie

Poznano typowe pułapki związane z równoległością:

  • Zachowywanie kolejności (forEachOrdered, limit, findFirst) zwiększa koszt koordynacji; gdy to możliwe, należy używać unordered lub findAny.
  • Boxing zwiększa koszt; należy preferować strumienie typów prostych.
  • Wspólna pula może zostać zagłodzona; ciężką pracę należy izolować we własnej puli ForkJoinPool.
  • Źródła trudne do podziału, małe zbiory danych i kosztowne scalanie osłabiają korzyści z równoległości.

Często zadawane pytania

Czy lekcja „Typowe pułapki” jest bezpłatna?

Tak — pełny tekst „Typowe pułapki” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Typowe pułapki”?

Kolejność, boxing i wspólna pula Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Typowe pułapki”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Tworzenie strumieni równoległych
  2. Kiedy równoległość pomaga
  3. Bezpieczeństwo wątkowe i efekty uboczne
  4. Typowe pułapki
← Powrót do Java Academy