Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте ForkJoinPool | Найкращі Практики Багатопотокового Програмування
Quizzes & Challenges
Quizzes
Challenges
/
Багатопотоковість у Java

bookForkJoinPool

Клас ForkJoinPool у Java для роботи з фреймворком Fork/Join є саме цим прикладом. Він надає механізми для керування завданнями, які можна розділити на менші підзавдання та виконувати паралельно.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool базується на концепції двох основних дій: fork та join. Fork — це дія, коли велике завдання розбивається на кілька менших підзавдань, які можуть бути виконані паралельно. Join — це процес, під час якого результати цих підзавдань об'єднуються для формування результату початкового завдання.

Приклад використання

Уявіть, що потрібно проаналізувати величезний набір даних, який можна розділити на кілька менших піднаборів. Якщо обробляти кожен піднабір окремо, а потім об'єднати результати, це дозволяє значно прискорити обробку, особливо на багатопроцесорних системах.

Як використовувати ForkJoinPool

Існує 2 класи для реалізації завдань: RecursiveTask та RecursiveAction. В обох необхідно реалізувати абстрактний метод compute(). У RecursiveTask метод compute() повертає значення, а у RecursiveAction метод compute() повертає void.

Main.java

Main.java

copy
12345678
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }

Коли ми наслідуємо від RecursiveTask, необхідно вказати, який тип даних метод compute() буде повертати, використовуючи такий синтаксис: RecursiveTask<String>.

Main.java

Main.java

copy
1234567
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }

Тут навпаки — не потрібно явно вказувати тип у RecursiveAction, оскільки наш метод нічого не повертає і лише виконує завдання.

Запуск завдання

До речі, можна запустити завдання на виконання навіть без використання ForkJoinPool, просто використовуючи методи fork() та join().

Важливо зазначити, що метод fork() надсилає завдання до певного потоку. Метод join() використовується для отримання результату.

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }

Основні методи ForkJoinPool

  • invoke(): Запускає завдання у пулі та очікує його завершення. Повертає результат виконання завдання;
  • submit(): Передає завдання у пул, але не блокує поточний потік під час очікування завершення завдання. Може використовуватися для передачі завдань і отримання їх об'єктів Future;
  • execute(): Виконує завдання у пулі, але не повертає результат і не блокує поточний потік.

Як можна запустити задачу за допомогою ForkJoinPool, дуже просто!

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }

Але в чому різниця між запуском через fork(), join() та через клас ForkJoinPool?

Дуже просто! При використанні першого способу за допомогою методів fork(), join() задача запускається у тому ж потоці, в якому були викликані ці методи, блокуючи цей потік, тоді як за допомогою класу ForkJoinPool виділяється потік з пулу, і задача виконується у цьому потоці без блокування головного потоку.

Розгляньмо детальніше, припустимо, маємо таку реалізацію:

Main.java

Main.java

copy
12345678
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }

І ми хочемо виконати цей код за допомогою fork() та join(). Подивімося, що буде виведено в консоль і який потік виконуватиме це завдання.

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }

І ми отримуємо такий вивід у консоль:

Thread: main
Wow, it works!!!

Тепер розглянемо, що відбувається, якщо запустити з ForkJoinPool:

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }

Отримуємо такий висновок:

Thread: ForkJoinPool-1-worker-1
Wow, it works!!!

Як видно, різниця полягає в тому, що при використанні першого методу завдання виконується в тому ж потоці, який його викликав (головний потік). Але якщо використовується другий метод, потік береться з пулу потоків ForkJoinPool, і головний потік, який викликав цю логіку, не блокується і продовжує виконання!

Як реалізувати Fork/Join у коді

Найпростіше пояснити це за допомогою відео, а не наводити вам 50-80 рядків коду та розбирати їх поетапно.

Note
Примітка

ForkJoinPool ефективний для задач, які можна легко розділити на менші підзадачі. Однак, якщо підзадачі надто дрібні, використання ForkJoinPool може не дати суттєвого приросту продуктивності.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 4. Розділ 2

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Awesome!

Completion rate improved to 3.33

bookForkJoinPool

Свайпніть щоб показати меню

Клас ForkJoinPool у Java для роботи з фреймворком Fork/Join є саме цим прикладом. Він надає механізми для керування завданнями, які можна розділити на менші підзавдання та виконувати паралельно.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool базується на концепції двох основних дій: fork та join. Fork — це дія, коли велике завдання розбивається на кілька менших підзавдань, які можуть бути виконані паралельно. Join — це процес, під час якого результати цих підзавдань об'єднуються для формування результату початкового завдання.

Приклад використання

Уявіть, що потрібно проаналізувати величезний набір даних, який можна розділити на кілька менших піднаборів. Якщо обробляти кожен піднабір окремо, а потім об'єднати результати, це дозволяє значно прискорити обробку, особливо на багатопроцесорних системах.

Як використовувати ForkJoinPool

Існує 2 класи для реалізації завдань: RecursiveTask та RecursiveAction. В обох необхідно реалізувати абстрактний метод compute(). У RecursiveTask метод compute() повертає значення, а у RecursiveAction метод compute() повертає void.

Main.java

Main.java

copy
12345678
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }

Коли ми наслідуємо від RecursiveTask, необхідно вказати, який тип даних метод compute() буде повертати, використовуючи такий синтаксис: RecursiveTask<String>.

Main.java

Main.java

copy
1234567
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }

Тут навпаки — не потрібно явно вказувати тип у RecursiveAction, оскільки наш метод нічого не повертає і лише виконує завдання.

Запуск завдання

До речі, можна запустити завдання на виконання навіть без використання ForkJoinPool, просто використовуючи методи fork() та join().

Важливо зазначити, що метод fork() надсилає завдання до певного потоку. Метод join() використовується для отримання результату.

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }

Основні методи ForkJoinPool

  • invoke(): Запускає завдання у пулі та очікує його завершення. Повертає результат виконання завдання;
  • submit(): Передає завдання у пул, але не блокує поточний потік під час очікування завершення завдання. Може використовуватися для передачі завдань і отримання їх об'єктів Future;
  • execute(): Виконує завдання у пулі, але не повертає результат і не блокує поточний потік.

Як можна запустити задачу за допомогою ForkJoinPool, дуже просто!

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }

Але в чому різниця між запуском через fork(), join() та через клас ForkJoinPool?

Дуже просто! При використанні першого способу за допомогою методів fork(), join() задача запускається у тому ж потоці, в якому були викликані ці методи, блокуючи цей потік, тоді як за допомогою класу ForkJoinPool виділяється потік з пулу, і задача виконується у цьому потоці без блокування головного потоку.

Розгляньмо детальніше, припустимо, маємо таку реалізацію:

Main.java

Main.java

copy
12345678
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }

І ми хочемо виконати цей код за допомогою fork() та join(). Подивімося, що буде виведено в консоль і який потік виконуватиме це завдання.

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }

І ми отримуємо такий вивід у консоль:

Thread: main
Wow, it works!!!

Тепер розглянемо, що відбувається, якщо запустити з ForkJoinPool:

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }

Отримуємо такий висновок:

Thread: ForkJoinPool-1-worker-1
Wow, it works!!!

Як видно, різниця полягає в тому, що при використанні першого методу завдання виконується в тому ж потоці, який його викликав (головний потік). Але якщо використовується другий метод, потік береться з пулу потоків ForkJoinPool, і головний потік, який викликав цю логіку, не блокується і продовжує виконання!

Як реалізувати Fork/Join у коді

Найпростіше пояснити це за допомогою відео, а не наводити вам 50-80 рядків коду та розбирати їх поетапно.

Note
Примітка

ForkJoinPool ефективний для задач, які можна легко розділити на менші підзадачі. Однак, якщо підзадачі надто дрібні, використання ForkJoinPool може не дати суттєвого приросту продуктивності.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 4. Розділ 2
some-alt