window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster już istnieje') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Współbieżność w Javie Część 1 - Wprowadzenie - The Codest
The Codest
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Branże
    • Fintech i bankowość
    • E-commerce
    • Adtech
    • Healthtech
    • Produkcja
    • Logistyka
    • Motoryzacja
    • IOT
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
Strzałka w tył WSTECZ
2022-06-15
Software Development

Współbieżność w Javie Część 1 - Wprowadzenie

The Codest

Rafał Sawicki

Java Developer

Przeczytaj pierwszą część naszej serii blogów poświęconych współbieżności w Javie. W kolejnym artykule przyjrzymy się bliżej różnicom między wątkami i procesami, pulom wątków, executorom i wielu innym!

Ogólnie rzecz biorąc, konwencjonalne podejście do programowania jest sekwencyjne. Wszystko w programie dzieje się krok po kroku.
Ale w rzeczywistości równoległość jest sposobem, w jaki działa cały świat - jest to zdolność do wykonywania więcej niż jednego zadania jednocześnie.

Wątek a proces

Omówienie takich zaawansowanych tematów jak współbieżność w Java lub wielowątkowość, musimy ustalić pewne wspólne definicje, aby mieć pewność, że jesteśmy po tej samej stronie.

Zacznijmy od podstaw. W niesekwencyjnym świecie, mamy dwa rodzaje reprezentantów współbieżności: procesy oraz
wątki. Proces to instancja uruchomionego programu. Zazwyczaj jest on odizolowany od innych procesów.
System operacyjny jest odpowiedzialny za przypisywanie zasobów do każdego procesu. Ponadto działa jako przewodnik, który
harmonogramy i kontroluje je.

Wątek jest rodzajem procesu, ale na niższym poziomie, dlatego jest również znany jako lekki wątek. Wiele wątków może działać w jednym
proces. W tym przypadku program działa jako harmonogram i kontroler wątków. W ten sposób poszczególne programy wydają się wykonywać
wiele zadań w tym samym czasie.

Podstawową różnicą między wątkami a procesami jest poziom izolacji. Proces ma swój własny zestaw
podczas gdy wątek współdzieli dane z innymi wątkami. Może się to wydawać podejściem podatnym na błędy i rzeczywiście tak jest. Dla
Nie skupiajmy się teraz na tym, ponieważ wykracza to poza zakres tego artykułu.

Procesy, wątki - ok... Ale czym dokładnie jest współbieżność? Współbieżność oznacza możliwość wykonywania wielu zadań jednocześnie.
czasu. Nie oznacza to, że zadania te muszą być wykonywane jednocześnie - na tym polega równoległość. Concurrenc w Javay również nie
wymaga posiadania wielu procesorów lub nawet wielu rdzeni. Można to osiągnąć w środowisku jednordzeniowym, wykorzystując
przełączanie kontekstu.

Termin związany ze współbieżnością to wielowątkowość. Jest to cecha programów, która pozwala im wykonywać kilka zadań jednocześnie. Nie każdy program korzysta z tego podejścia, ale te, które to robią, można nazwać wielowątkowymi.

Jesteśmy prawie gotowi, jeszcze tylko jedna definicja. Asynchronia oznacza, że program wykonuje operacje bez blokowania.
Inicjuje zadanie, a następnie wykonuje inne czynności w oczekiwaniu na odpowiedź. Gdy otrzyma odpowiedź, może na nią zareagować.

Cały ten jazz

Domyślnie każdy Aplikacja Java działa w jednym procesie. W procesie tym istnieje jeden wątek związany z aplikacją main() metoda
aplikacji. Jak jednak wspomniano, możliwe jest wykorzystanie mechanizmów wielowątkowości w ramach jednej aplikacji.
program.

Wykonywalny

Wątek jest Java w której dzieje się magia. Jest to reprezentacja obiektowa wcześniej wspomnianego wątku. Do
utworzyć własny wątek, można rozszerzyć Wątek klasa. Nie jest to jednak zalecane podejście. Nici powinien być używany jako mechanizm uruchamiający zadanie. Zadania są elementami kod które chcemy uruchomić w trybie współbieżnym. Możemy je zdefiniować za pomocą funkcji Wykonywalny interfejs.

Ale dość teorii, włóżmy nasz kod tam, gdzie są nasze usta.

Problem

Załóżmy, że mamy kilka tablic liczb. Dla każdej tablicy chcemy poznać sumę liczb w tablicy. Załóżmy
Załóżmy, że takich tablic jest wiele i każda z nich jest stosunkowo duża. W takich warunkach chcemy wykorzystać współbieżność i zsumować każdą tablicę jako osobne zadanie.

int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10};;
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};

Runnable task1 = () -> {
    int sum = Arrays.stream(a1).sum();
    System.out.println("1. Suma wynosi: " + sum);
};

Runnable task2 = () -> {
    int sum = Arrays.stream(a2).sum();
    System.out.println("2. Suma wynosi: " + sum);
};

Runnable task3 = () -> {
    int sum = Arrays.stream(a3).sum();
    System.out.println("3. Suma wynosi: " + sum);
};

new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();

Jak widać z powyższego kodu Wykonywalny jest interfejsem funkcjonalnym. Zawiera jedną abstrakcyjną metodę run()
bez argumentów. The Wykonywalny Interfejs powinien być implementowany przez każdą klasę, której instancje mają być
wykonywane przez wątek.

Po zdefiniowaniu zadania można utworzyć wątek do jego uruchomienia. Można to osiągnąć poprzez new Thread() konstruktor, który
bierze Wykonywalny jako argument.

Ostatnim krokiem jest start() nowo utworzonego wątku. W API dostępne są również run() metody w Wykonywalny oraz w
Wątek. Nie jest to jednak sposób na wykorzystanie współbieżności w Javie. Bezpośrednie wywołanie każdej z tych metod skutkuje
wykonanie zadania w tym samym wątku main() metoda działa.

Pule wątków i executorzy

W przypadku dużej liczby zadań tworzenie osobnego wątku dla każdego z nich nie jest dobrym pomysłem. Tworzenie wątku Wątek jest
i znacznie lepiej jest ponownie wykorzystać istniejące wątki niż tworzyć nowe.

Gdy program tworzy wiele krótkotrwałych wątków, lepiej jest użyć puli wątków. Pula wątków zawiera pewną liczbę
gotowych do uruchomienia, ale obecnie nieaktywnych wątków. Dając Wykonywalny do puli powoduje, że jeden z wątków wywołuje funkcję
run() metoda danego Wykonywalny. Po zakończeniu zadania wątek nadal istnieje i znajduje się w stanie bezczynności.

Ok, jasne - wolisz pulę wątków zamiast ręcznego tworzenia. Ale jak można wykorzystać pule wątków? The Wykonawcy
posiada szereg statycznych metod fabrycznych do konstruowania pul wątków. Na przykład newCachedThredPool() tworzy
pula, w której nowe wątki są tworzone w razie potrzeby, a bezczynne wątki są przechowywane przez 60 sekund. W przeciwieństwie do tego,
newFixedThreadPool() zawiera stały zestaw wątków, w którym bezczynne wątki są przechowywane w nieskończoność.

Zobaczmy, jak to może działać w naszym przykładzie. Teraz nie musimy tworzyć wątków ręcznie. Zamiast tego musimy utworzyć
ExecutorService która zapewnia pulę wątków. Następnie możemy przypisać do niego zadania. Ostatnim krokiem jest zamknięcie wątku
aby uniknąć wycieków pamięci. Reszta poprzedniego kodu pozostaje bez zmian.

ExecutorService executor = Executors.newCachedThreadPool();

executor.submit(task1);
executor.submit(task2);
executor.submit(task3);

executor.shutdown();

Na żądanie

Wykonywalny wydaje się fajnym sposobem na tworzenie współbieżnych zadań, ale ma jedną poważną wadę. Nie może zwrócić żadnego
wartość. Co więcej, nie możemy określić, czy zadanie zostało ukończone, czy nie. Nie wiemy też, czy zostało ono ukończone
normalnie lub wyjątkowo. Rozwiązaniem tych bolączek jest Na żądanie.

Na żądanie jest podobny do Wykonywalny w pewnym sensie również opakowuje zadania asynchroniczne. Główną różnicą jest to, że jest w stanie
zwraca wartość. Zwracana wartość może być dowolnego (nieprymitywnego) typu, tak jak funkcja Na żądanie jest typem sparametryzowanym.
Na żądanie jest funkcjonalnym interfejsem, który posiada call() która może rzucić metodę Wyjątek.

Zobaczmy teraz, jak możemy wykorzystać Na żądanie w naszym problemie z tablicą.

int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10};;
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};

Callable task1 = () -> Arrays.stream(a1).sum();
Callable task2 = () -> Arrays.stream(a2).sum();
Callable task3 = () -> Arrays.stream(a3).sum();

ExecutorService executor = Executors.newCachedThreadPool();
Future future1 = executor.submit(task1);
Future future2 = executor.submit(task2);
Future future3 = executor.submit(task3);

System.out.println("1. Suma wynosi: " + future1.get());
System.out.println("2. Suma wynosi: " + future2.get());
System.out.println("3. Suma wynosi: " + future3.get());

executor.shutdown();

Ok, możemy zobaczyć jak Na żądanie jest tworzony, a następnie przesyłany do ExecutorService. Ale czym do cholery jest Przyszłość?
Przyszłość działa jako pomost między wątkami. Suma każdej tablicy jest tworzona w oddzielnym wątku i potrzebujemy sposobu na
otrzymanie tych wyników z powrotem do main().

Aby pobrać wynik z Przyszłość musimy zadzwonić get() metoda. Tutaj może wydarzyć się jedna z dwóch rzeczy. Po pierwsze
wynik obliczeń wykonanych przez Na żądanie jest dostępny. Wtedy otrzymujemy go natychmiast. Po drugie, wynik nie jest
jeszcze gotowy. W takim przypadku get() będzie blokowana do momentu uzyskania wyniku.

ComputableFuture

Problem z Przyszłość jest to, że działa w "paradygmacie push". Podczas korzystania z Przyszłość musisz być jak szef, który
nieustannie pyta: "Czy zadanie zostało wykonane? Czy jest gotowe?", dopóki nie przyniesie rezultatu. Działanie pod ciągłą presją to
drogie. O wiele lepszym podejściem byłoby zamówienie Przyszłość co zrobić, gdy będzie gotowy do wykonania swojego zadania. Niestety,
Przyszłość nie może tego zrobić, ale ComputableFuture może.

ComputableFuture działa w paradygmacie "pull". Możemy mu powiedzieć, co ma zrobić z wynikiem, gdy wykona swoje zadania. To
jest przykładem podejścia asynchronicznego.

ComputableFuture działa idealnie z Wykonywalny ale nie z Na żądanie. Zamiast tego możliwe jest dostarczenie zadania do
ComputableFuture w postaci Dostawca.

Zobaczmy, jak powyższe odnosi się do naszego problemu.

int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10};;
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};

CompletableFuture.supplyAsync(() -> Arrays.stream(a1).sum())
                .thenAccept(System.out::println);

CompletableFuture.supplyAsync(() -> Arrays.stream(a2).sum())
                .thenAccept(System.out::println);

CompletableFuture.supplyAsync(() -> Arrays.stream(a3).sum())
                .thenAccept(System.out::println);

Pierwszą rzeczą, która rzuca się w oczy, jest to, o ile krótsze jest to rozwiązanie. Poza tym wygląda schludnie i estetycznie.

Zadanie do CompletableFuture mogą być dostarczane przez supplyAsync() metoda, która przyjmuje Dostawca lub przez runAsync() że
bierze Wykonywalny. Wywołanie zwrotne - fragment kodu, który powinien zostać uruchomiony po zakończeniu zadania - jest definiowane przez thenAccept()
metoda.

Wnioski

Java zapewnia wiele różnych podejść do współbieżności. W tym artykule ledwie dotknęliśmy tego tematu.

Niemniej jednak, omówiliśmy podstawy Wątek, Wykonywalny, Na żądanieoraz CallableFuture co stanowi dobry punkt
do dalszego badania tematu.

Powiązane artykuły

Software Development

9 błędów, których należy unikać podczas programowania w Javie

Jakich błędów należy unikać podczas programowania w Javie? W poniższym artykule odpowiemy na to pytanie.

The Codest
Rafał Sawicki Java Developer
Rozwiązania dla przedsiębiorstw i scaleupów

Właściwy sposób na znalezienie najlepszych programistów Java

Znalezienie idealnego programisty Java może być trudnym zadaniem. Ponieważ zapotrzebowanie rynku na takich specjalistów rośnie w zaskakującym tempie, dostępne źródła poszukiwania talentów mogą czasami wydawać się...

The Codest
Grzegorz Rozmus Java Unit Leader
Rozwiązania dla przedsiębiorstw i scaleupów

10 firm z Dubaju, które warto obserwować w 2020 r.

Dubaj jest sercem Zjednoczonych Emiratów Arabskich z coraz lepiej prosperującym rynkiem globalnych firm i obiecujących startupów. Wiele z nich może pochwalić się międzynarodowym sukcesem i godnymi uwagi produktami....

Tuna Pinar
Rozwiązania dla przedsiębiorstw i scaleupów

Jak Java może wesprzeć Twój biznes?

Zanim zaczniemy, chciałbym przypomnieć o jednej ważnej rzeczy. Java to nie tylko język programowania.

Bartłomiej Kuczyński
Rozwiązania dla przedsiębiorstw i scaleupów

Dzień z życia programisty w The Codest

Można podejrzewać, że harmonogramy pracy programistów nie różnią się od siebie. Ale w rzeczywistości nie jest to prawdą! Każdy startup, software house, a nawet korporacja ma swój własny...

The Codest
Paweł Rybczyński Software Engineer

Subskrybuj naszą bazę wiedzy i bądź na bieżąco!

    O nas

    The Codest - Międzynarodowa firma programistyczna z centrami technologicznymi w Polsce.

    Wielka Brytania - siedziba główna

    • Office 303B, 182-184 High Street North E6 2JA
      Londyn, Anglia

    Polska - lokalne centra technologiczne

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polska

      The Codest

    • Strona główna
    • O nas
    • Nasze Usługi
    • Case Studies
    • Nasze Know How
    • Kariera
    • Słownik

      Nasze Usługi

    • Konsultacje IT
    • Software Development
    • Backend Development
    • Frontend Development
    • Zespoły IT
    • Backend Dev
    • Inżynierowie rozwiązań chmurowych
    • Inżynierowie danych
    • Inne
    • Inżynierowie QA

      Raporty

    • Fakty i mity na temat współpracy z zewnętrznym partnerem programistycznym
    • Z USA do Europy: Dlaczego amerykańskie startupy decydują się na relokację do Europy?
    • Porównanie centrów rozwoju Tech Offshore: Tech Offshore Europa (Polska), ASEAN (Filipiny), Eurazja (Turcja)
    • Jakie są największe wyzwania CTO i CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Warunki korzystania z witryny

    Copyright © 2025 by The Codest. Wszelkie prawa zastrzeżone.

    pl_PLPolish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek pl_PLPolish