Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Oppiskele Forkjoinpool | Monisäikeisyyden Parhaat Käytännöt
Monisäikeisyys Javassa

bookForkjoinpool

ForkJoinPool-luokka Javassa Fork/Join-kehystä varten on juuri tämän toteutus. Se tarjoaa mekanismit tehtävien hallintaan, jotka voidaan jakaa pienempiin osatehtäviin ja suorittaa rinnakkain.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool perustuu kahteen perustoimintoon: fork ja join. Fork tarkoittaa toimintoa, jossa suuri tehtävä jaetaan useisiin pienempiin osatehtäviin, jotka voidaan suorittaa rinnakkain. Join on prosessi, jossa näiden osatehtävien tulokset yhdistetään alkuperäisen tehtävän tulokseksi.

Tapaustutkimus

Kuvittele, että sinun täytyy analysoida valtava tietoaineisto, joka voidaan jakaa useisiin pienempiin osiin. Jos käsittelet jokaisen tietoaineiston erikseen ja sitten yhdistät tulokset, voit nopeuttaa käsittelyä merkittävästi, erityisesti moniprosessorijärjestelmissä.

ForkJoinPoolin käyttö

On olemassa 2 luokkaa, joilla toteutetaan tehtäviä: RecursiveTask ja RecursiveAction. Molemmat vaativat abstraktin compute()-metodin toteutuksen. RecursiveTask-luokassa compute()-metodi palauttaa arvon, kun taas RecursiveAction-luokassa compute()-metodi ei palauta arvoa.

Main.java

Main.java

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

Kun perimme luokasta RecursiveTask, meidän on varmistettava, että määrittelemme palautettavan tietotyypin compute()-metodille käyttämällä syntaksia RecursiveTask<String>.

Main.java

Main.java

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

Tässä tapauksessa päinvastoin, meidän ei tarvitse määritellä tyyppiä erikseen RecursiveAction-luokassa, koska metodimme ei palauta mitään, vaan suorittaa vain tehtävän.

Tehtävän käynnistäminen

Voimme muuten käynnistää tehtävän suoritettavaksi ilman ForkJoinPool-luokan käyttöä, pelkästään käyttämällä fork()- ja join()-menetelmiä.

On tärkeää huomata, että fork()-menetelmä lähettää tehtävän jollekin säikeelle. join()-menetelmää käytetään tuloksen hakemiseen.

Main.java

Main.java

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

ForkJoinPoolin päämenetelmät

  • invoke(): Käynnistää tehtävän poolissa ja odottaa sen valmistumista. Palauttaa tehtävän suorituksen tuloksen;
  • submit(): Lähettää tehtävän pooliin, mutta ei estä nykyistä säiettä odottamaan tehtävän valmistumista. Voidaan käyttää tehtävien lähettämiseen ja niiden Future-olioiden hakemiseen;
  • execute(): Suorittaa tehtävän poolissa, mutta ei palauta tulosta eikä estä nykyistä säiettä.

Kuinka voimme käynnistää tehtävän käyttäen ForkJoinPool, hyvin yksinkertaista!

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

Mutta mikä on ero suorittaa fork(), join() ja käyttää ForkJoinPool -luokkaa?

Se on hyvin yksinkertaista! Kun käytetään ensimmäistä menetelmää fork()- ja join()-metodien avulla, tehtävä käynnistetään samassa säikeessä, jossa nämä metodit kutsutaan, tukien tämän säikeen, kun taas ForkJoinPool-luokan avulla säie varataan säiepoolista ja se suorittaa tehtävän tässä säikeessä ilman, että pääsäiettä tukitaan.

Tarkastellaanpa tarkemmin, oletetaan että meillä on tällainen toteutus:

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

Ja haluamme suorittaa tämän koodin käyttäen fork() ja join(). Katsotaanpa, mitä tulostuu konsoliin ja mikä säie suorittaa tämän tehtävän.

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

Ja saamme seuraavan tulosteen konsoliin:

Thread: main
Wow, it works!!!

Tarkastellaan nyt, mitä tapahtuu, kun ajetaan ForkJoinPool-luokalla:

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

Ja tästä saadaan seuraava johtopäätös:

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

Kuten huomaat, ero on siinä, että käytettäessä ensimmäistä menetelmää tehtävä suoritetaan samassa säikeessä, joka kutsui tämän tehtävän (pääsäie). Mutta jos käytämme toista menetelmää, säie otetaan ForkJoinPool-säiepoolista ja pääsäie, joka kutsui tämän logiikan, ei esty vaan jatkaa eteenpäin!

Kuinka toteuttaa Fork/Join koodissa

Tätä on helpointa havainnollistaa videolla sen sijaan, että annettaisiin 50–80 riviä koodia ja selitettäisiin sitä kohta kohdalta.

Note
Huomio

ForkJoinPool soveltuu tehokkaasti tehtäviin, jotka voidaan helposti jakaa pienempiin osatehtäviin. Jos tehtävät ovat kuitenkin liian pieniä, ForkJoinPool-ratkaisun käyttö ei välttämättä tuo merkittävää suorituskykyetua.

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 4. Luku 2

Kysy tekoälyä

expand

Kysy tekoälyä

ChatGPT

Kysy mitä tahansa tai kokeile jotakin ehdotetuista kysymyksistä aloittaaksesi keskustelumme

Awesome!

Completion rate improved to 3.33

bookForkjoinpool

Pyyhkäise näyttääksesi valikon

ForkJoinPool-luokka Javassa Fork/Join-kehystä varten on juuri tämän toteutus. Se tarjoaa mekanismit tehtävien hallintaan, jotka voidaan jakaa pienempiin osatehtäviin ja suorittaa rinnakkain.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool perustuu kahteen perustoimintoon: fork ja join. Fork tarkoittaa toimintoa, jossa suuri tehtävä jaetaan useisiin pienempiin osatehtäviin, jotka voidaan suorittaa rinnakkain. Join on prosessi, jossa näiden osatehtävien tulokset yhdistetään alkuperäisen tehtävän tulokseksi.

Tapaustutkimus

Kuvittele, että sinun täytyy analysoida valtava tietoaineisto, joka voidaan jakaa useisiin pienempiin osiin. Jos käsittelet jokaisen tietoaineiston erikseen ja sitten yhdistät tulokset, voit nopeuttaa käsittelyä merkittävästi, erityisesti moniprosessorijärjestelmissä.

ForkJoinPoolin käyttö

On olemassa 2 luokkaa, joilla toteutetaan tehtäviä: RecursiveTask ja RecursiveAction. Molemmat vaativat abstraktin compute()-metodin toteutuksen. RecursiveTask-luokassa compute()-metodi palauttaa arvon, kun taas RecursiveAction-luokassa compute()-metodi ei palauta arvoa.

Main.java

Main.java

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

Kun perimme luokasta RecursiveTask, meidän on varmistettava, että määrittelemme palautettavan tietotyypin compute()-metodille käyttämällä syntaksia RecursiveTask<String>.

Main.java

Main.java

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

Tässä tapauksessa päinvastoin, meidän ei tarvitse määritellä tyyppiä erikseen RecursiveAction-luokassa, koska metodimme ei palauta mitään, vaan suorittaa vain tehtävän.

Tehtävän käynnistäminen

Voimme muuten käynnistää tehtävän suoritettavaksi ilman ForkJoinPool-luokan käyttöä, pelkästään käyttämällä fork()- ja join()-menetelmiä.

On tärkeää huomata, että fork()-menetelmä lähettää tehtävän jollekin säikeelle. join()-menetelmää käytetään tuloksen hakemiseen.

Main.java

Main.java

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

ForkJoinPoolin päämenetelmät

  • invoke(): Käynnistää tehtävän poolissa ja odottaa sen valmistumista. Palauttaa tehtävän suorituksen tuloksen;
  • submit(): Lähettää tehtävän pooliin, mutta ei estä nykyistä säiettä odottamaan tehtävän valmistumista. Voidaan käyttää tehtävien lähettämiseen ja niiden Future-olioiden hakemiseen;
  • execute(): Suorittaa tehtävän poolissa, mutta ei palauta tulosta eikä estä nykyistä säiettä.

Kuinka voimme käynnistää tehtävän käyttäen ForkJoinPool, hyvin yksinkertaista!

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

Mutta mikä on ero suorittaa fork(), join() ja käyttää ForkJoinPool -luokkaa?

Se on hyvin yksinkertaista! Kun käytetään ensimmäistä menetelmää fork()- ja join()-metodien avulla, tehtävä käynnistetään samassa säikeessä, jossa nämä metodit kutsutaan, tukien tämän säikeen, kun taas ForkJoinPool-luokan avulla säie varataan säiepoolista ja se suorittaa tehtävän tässä säikeessä ilman, että pääsäiettä tukitaan.

Tarkastellaanpa tarkemmin, oletetaan että meillä on tällainen toteutus:

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

Ja haluamme suorittaa tämän koodin käyttäen fork() ja join(). Katsotaanpa, mitä tulostuu konsoliin ja mikä säie suorittaa tämän tehtävän.

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

Ja saamme seuraavan tulosteen konsoliin:

Thread: main
Wow, it works!!!

Tarkastellaan nyt, mitä tapahtuu, kun ajetaan ForkJoinPool-luokalla:

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

Ja tästä saadaan seuraava johtopäätös:

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

Kuten huomaat, ero on siinä, että käytettäessä ensimmäistä menetelmää tehtävä suoritetaan samassa säikeessä, joka kutsui tämän tehtävän (pääsäie). Mutta jos käytämme toista menetelmää, säie otetaan ForkJoinPool-säiepoolista ja pääsäie, joka kutsui tämän logiikan, ei esty vaan jatkaa eteenpäin!

Kuinka toteuttaa Fork/Join koodissa

Tätä on helpointa havainnollistaa videolla sen sijaan, että annettaisiin 50–80 riviä koodia ja selitettäisiin sitä kohta kohdalta.

Note
Huomio

ForkJoinPool soveltuu tehokkaasti tehtäviin, jotka voidaan helposti jakaa pienempiin osatehtäviin. Jos tehtävät ovat kuitenkin liian pieniä, ForkJoinPool-ratkaisun käyttö ei välttämättä tuo merkittävää suorituskykyetua.

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 4. Luku 2
some-alt