Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lære Forkjoinpool | Bedste Praksis for Multitrådning
Quizzes & Challenges
Quizzes
Challenges
/
Multitrådning i Java

bookForkjoinpool

ForkJoinPool-klassen i Java til arbejde med Fork/Join-frameworket er netop realiseringen af dette. Den tilbyder mekanismer til håndtering af opgaver, der kan opdeles i mindre delopgaver og udføres parallelt.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool er baseret på konceptet om to grundlæggende handlinger: fork og join. Fork er en handling, hvor en stor opgave opdeles i flere mindre delopgaver, der kan udføres parallelt. Join er processen, hvor resultaterne af disse delopgaver kombineres for at danne resultatet af den oprindelige opgave.

Case Study

Forestil dig, at du skal analysere et enormt datasæt, som kan opdeles i flere mindre sæt. Hvis hvert datasæt behandles separat og resultaterne derefter sammenflettes, kan behandlingen fremskyndes betydeligt, især på multiprocessorsystemer.

Sådan bruges ForkJoinPool

Der findes 2 klasser til implementering af opgaver i dem, nemlig RecursiveTask og RecursiveAction. Begge kræver, at den abstrakte metode compute() implementeres. I RecursiveTask returnerer metoden compute() en værdi, mens RecursiveAction-metoden i compute() returnerer void.

Main.java

Main.java

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

Når vi arver fra en RecursiveTask, skal vi sikre os at angive, hvilken datatype vores compute()-metode vil returnere ved at bruge denne syntaks RecursiveTask<String>.

Main.java

Main.java

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

Her er det omvendt, vi behøver ikke at angive typen eksplicit i RecursiveAction, fordi vores metode ikke returnerer noget og blot udfører opgaven.

Start af en opgave

Bemærk, at vi kan starte en opgave til udførelse uden at bruge ForkJoinPool, blot ved at anvende metoderne fork() og join().

Det er vigtigt at bemærke, at metoden fork() sender opgaven til en tråd. Metoden join() bruges til at hente resultatet.

Main.java

Main.java

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

ForkJoinPool Hovedmetoder

  • invoke(): Starter en opgave i poolen og venter på, at den fuldføres. Returnerer resultatet af opgavens fuldførelse;
  • submit(): Indsender en opgave til poolen, men blokerer ikke den aktuelle tråd, mens den venter på, at opgaven fuldføres. Kan bruges til at indsende opgaver og hente deres Future-objekter;
  • execute(): Kører en opgave i poolen, men returnerer ikke et resultat og blokerer ikke den aktuelle tråd.

Hvordan kan vi starte en opgave ved hjælp af ForkJoinPool, meget enkelt!

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)); }

Men hvad er forskellen på at køre fork(), join() og via ForkJoinPool klassen?

Det er meget enkelt! Når man bruger den første metode med fork(), join() metoderne, starter vi opgaven i den samme tråd, hvor disse metoder blev kaldt, hvilket blokerer denne tråd, mens vi med ForkJoinPool klassen får tildelt en tråd fra puljen, og den arbejder i denne tråd uden at blokere hovedtråden.

Lad os se nærmere på det, antag at vi har en sådan implementering:

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!!!"; } }

Og vi ønsker at køre denne kode ved hjælp af fork() og join(). Lad os se, hvad der vil blive udskrevet til konsollen, og hvilken tråd der vil udføre denne opgave.

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()); } }

Og vi får denne output i konsollen:

Thread: main
Wow, it works!!!

Lad os nu se, hvad der sker, hvis vi kører med 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)); } }

Og vi får denne konklusion:

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

Som det kan ses, er forskellen at ved brug af første metode udføres opgaven i den samme tråd, der kaldte denne opgave (Main thread). Men hvis vi bruger anden metode, tages tråden fra ForkJoinPool tråd-poolen, og hovedtråden, der kaldte denne logik, blokeres ikke og fortsætter!

Sådan implementeres Fork/Join i kode

Den nemmeste måde at forklare dette på ville være i en video, frem for at give dig 50-80 linjer kode og forklare det punkt for punkt.

Note
Bemærk

ForkJoinPool er effektiv til opgaver, der let kan opdeles i mindre delopgaver. Hvis opgaverne dog er for små, kan brugen af ForkJoinPool ikke give en væsentlig ydelsesforbedring.

Var alt klart?

Hvordan kan vi forbedre det?

Tak for dine kommentarer!

Sektion 4. Kapitel 2

Spørg AI

expand

Spørg AI

ChatGPT

Spørg om hvad som helst eller prøv et af de foreslåede spørgsmål for at starte vores chat

Suggested prompts:

Can you explain the difference between RecursiveTask and RecursiveAction again?

How do I decide what threshold to use when splitting tasks?

Can you give a simple code example of using ForkJoinPool?

Awesome!

Completion rate improved to 3.33

bookForkjoinpool

Stryg for at vise menuen

ForkJoinPool-klassen i Java til arbejde med Fork/Join-frameworket er netop realiseringen af dette. Den tilbyder mekanismer til håndtering af opgaver, der kan opdeles i mindre delopgaver og udføres parallelt.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool er baseret på konceptet om to grundlæggende handlinger: fork og join. Fork er en handling, hvor en stor opgave opdeles i flere mindre delopgaver, der kan udføres parallelt. Join er processen, hvor resultaterne af disse delopgaver kombineres for at danne resultatet af den oprindelige opgave.

Case Study

Forestil dig, at du skal analysere et enormt datasæt, som kan opdeles i flere mindre sæt. Hvis hvert datasæt behandles separat og resultaterne derefter sammenflettes, kan behandlingen fremskyndes betydeligt, især på multiprocessorsystemer.

Sådan bruges ForkJoinPool

Der findes 2 klasser til implementering af opgaver i dem, nemlig RecursiveTask og RecursiveAction. Begge kræver, at den abstrakte metode compute() implementeres. I RecursiveTask returnerer metoden compute() en værdi, mens RecursiveAction-metoden i compute() returnerer void.

Main.java

Main.java

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

Når vi arver fra en RecursiveTask, skal vi sikre os at angive, hvilken datatype vores compute()-metode vil returnere ved at bruge denne syntaks RecursiveTask<String>.

Main.java

Main.java

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

Her er det omvendt, vi behøver ikke at angive typen eksplicit i RecursiveAction, fordi vores metode ikke returnerer noget og blot udfører opgaven.

Start af en opgave

Bemærk, at vi kan starte en opgave til udførelse uden at bruge ForkJoinPool, blot ved at anvende metoderne fork() og join().

Det er vigtigt at bemærke, at metoden fork() sender opgaven til en tråd. Metoden join() bruges til at hente resultatet.

Main.java

Main.java

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

ForkJoinPool Hovedmetoder

  • invoke(): Starter en opgave i poolen og venter på, at den fuldføres. Returnerer resultatet af opgavens fuldførelse;
  • submit(): Indsender en opgave til poolen, men blokerer ikke den aktuelle tråd, mens den venter på, at opgaven fuldføres. Kan bruges til at indsende opgaver og hente deres Future-objekter;
  • execute(): Kører en opgave i poolen, men returnerer ikke et resultat og blokerer ikke den aktuelle tråd.

Hvordan kan vi starte en opgave ved hjælp af ForkJoinPool, meget enkelt!

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)); }

Men hvad er forskellen på at køre fork(), join() og via ForkJoinPool klassen?

Det er meget enkelt! Når man bruger den første metode med fork(), join() metoderne, starter vi opgaven i den samme tråd, hvor disse metoder blev kaldt, hvilket blokerer denne tråd, mens vi med ForkJoinPool klassen får tildelt en tråd fra puljen, og den arbejder i denne tråd uden at blokere hovedtråden.

Lad os se nærmere på det, antag at vi har en sådan implementering:

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!!!"; } }

Og vi ønsker at køre denne kode ved hjælp af fork() og join(). Lad os se, hvad der vil blive udskrevet til konsollen, og hvilken tråd der vil udføre denne opgave.

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()); } }

Og vi får denne output i konsollen:

Thread: main
Wow, it works!!!

Lad os nu se, hvad der sker, hvis vi kører med 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)); } }

Og vi får denne konklusion:

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

Som det kan ses, er forskellen at ved brug af første metode udføres opgaven i den samme tråd, der kaldte denne opgave (Main thread). Men hvis vi bruger anden metode, tages tråden fra ForkJoinPool tråd-poolen, og hovedtråden, der kaldte denne logik, blokeres ikke og fortsætter!

Sådan implementeres Fork/Join i kode

Den nemmeste måde at forklare dette på ville være i en video, frem for at give dig 50-80 linjer kode og forklare det punkt for punkt.

Note
Bemærk

ForkJoinPool er effektiv til opgaver, der let kan opdeles i mindre delopgaver. Hvis opgaverne dog er for små, kan brugen af ForkJoinPool ikke give en væsentlig ydelsesforbedring.

Var alt klart?

Hvordan kan vi forbedre det?

Tak for dine kommentarer!

Sektion 4. Kapitel 2
some-alt