Programovanie

Tip pre Java: Kedy použiť ForkJoinPool vs ExecutorService

Knižnica Fork / Join predstavená v prostredí Java 7 rozširuje existujúci balík súbežnosti Java o podporu hardvérového paralelizmu, čo je kľúčová vlastnosť viacjadrových systémov. V tomto tipe Java Madalin Ilie demonštruje vplyv nahradenia Java 6 na výkon ExecutorService trieda s Java 7 ForkJoinPool v aplikácii webového prehľadávača.

Webové prehľadávače, tiež známe ako pavúky webu, sú kľúčom k úspechu vyhľadávacích nástrojov. Tieto programy neustále skenujú web, zhromažďujú milióny stránok s údajmi a odosielajú ich späť do databáz vyhľadávacích nástrojov. Údaje sú potom indexované a spracované algoritmicky, čo vedie k rýchlejším a presnejším výsledkom vyhľadávania. Aj keď sa najznámejšie používajú na optimalizáciu vyhľadávania, webové prehľadávače sa dajú použiť aj na automatizované úlohy, ako je overenie odkazu alebo vyhľadanie a vrátenie konkrétnych údajov (napríklad e-mailových adries) v kolekcii webových stránok.

Architektonicky je väčšina webových prehľadávačov vysoko výkonné viacvláknové programy, aj keď s relatívne jednoduchou funkčnosťou a požiadavkami. Budovanie webového prehľadávača je preto zaujímavým spôsobom, ako si precvičiť a porovnať techniky programovania s viacerými vláknami alebo súčasne s nimi.

Návrat Java Tipy!

Java Tips sú krátke články založené na kódoch, ktoré vyzývajú čitateľov JavaWorld, aby sa podelili o svoje programovacie schopnosti a objavy. Dajte nám vedieť, ak máte tip na zdieľanie s komunitou JavaWorld. Prezrite si tiež Archív tipov Java, kde nájdete ďalšie tipy na programovanie od vašich kolegov.

V tomto článku si ukážem dva prístupy k písaniu webového prehľadávača: jeden používajúci Java 6 ExecutorService a druhý Java 7 ForkJoinPool. Aby ste mohli postupovať podľa príkladov, budete musieť mať vo svojom vývojovom prostredí nainštalovanú (od tohto písania) aktualizáciu Java 7 update 2 a tiež knižnicu HtmlParser od iného výrobcu.

Dva prístupy k súbežnosti Javy

The ExecutorService trieda je súčasťou java.util.concurrent revolúcia zavedená v prostredí Java 5 (a samozrejme v časti Java 6), ktorá zjednodušila prácu s vláknami na platforme Java. ExecutorService je exekútor, ktorý poskytuje metódy na riadenie sledovania pokroku a ukončenia asynchrónnych úloh. Pred zavedením java.util.concurrent, Vývojári Java sa spoliehali na knižnice tretích strán alebo si písali vlastné triedy, aby vo svojich programoch spravovali súbežnosť.

Fork / Join, predstavený v prostredí Java 7, nemá nahradiť alebo konkurovať existujúcim triedam súbežnosti; namiesto toho ich aktualizuje a dopĺňa. Fork / Join rieši potrebu rozdelenia a dobývania, príp rekurzívny spracovanie úloh v programoch Java (pozri Zdroje).

Logika vidlice / spojenia je veľmi jednoduchá: (1) každú veľkú úlohu rozdeľte (rozvetvte) na menšie; (2) spracujte každú úlohu v samostatnom vlákne (v prípade potreby ich rozdeľte na ešte menšie úlohy); (3) Pripojte sa k výsledkom.

Dve nasledujúce implementácie webového prehľadávača, ktoré nasledujú, sú jednoduché programy, ktoré demonštrujú vlastnosti a funkčnosť Java 6 ExecutorService a Java 7 ForkJoinPool.

Budovanie a porovnávanie webového prehľadávača

Úlohou nášho webového prehľadávača bude nájsť a sledovať odkazy. Jeho účelom môže byť overenie odkazu alebo zhromažďovanie údajov. (Môžete napríklad dať programu pokyn, aby prehľadal na webe obrázky Angeliny Jolie alebo Brada Pitta.)

Architektúra aplikácie pozostáva z týchto častí:

  1. Rozhranie, ktoré vystavuje základné operácie na interakciu s odkazmi; tj. získať počet navštívených odkazov, pridať nové odkazy, ktoré sa majú navštíviť, vo fronte, označiť odkaz ako navštívený
  2. Implementácia tohto rozhrania, ktorá bude tiež východiskovým bodom aplikácie
  3. Vlákno / rekurzívna akcia, ktorá bude obsahovať obchodnú logiku a skontroluje, či už odkaz bol navštívený. Ak nie, zhromaždí všetky odkazy na príslušnej stránke, vytvorí nové vlákno / rekurzívnu úlohu a odošle ju na ExecutorService alebo ForkJoinPool
  4. An ExecutorService alebo ForkJoinPool vybaviť čakajúce úlohy

Upozorňujeme, že odkaz sa považuje za „navštívený“ po vrátení všetkých odkazov na príslušnej stránke.

Okrem porovnania ľahkého vývoja pomocou nástrojov súbežnosti dostupných v prostredí Java 6 a Java 7 porovnáme výkonnosť aplikácie na základe dvoch kritérií:

  • Pokrytie vyhľadávania: Meria čas potrebný na návštevu 1 500 odlišný odkazy
  • Výkon spracovania: Meria čas v sekundách potrebný na návštevu 3 000 nerozdielny odkazy; to je ako meranie toho, koľko kilobitov za sekundu vaše pripojenie k internetu spracuje.

Aj keď sú relatívne jednoduché, tieto kritériá poskytnú aspoň malé okno do výkonu súbežnosti Java v prostredí Java 6 verzus Java 7 pre určité aplikačné požiadavky.

Webový prehľadávač Java 6 zostavený pomocou služby ExecutorService

Na implementáciu webového prehľadávača Java 6 použijeme fond pevných vlákien so 64 vláknami, ktorý vytvoríme zavolaním Executors.newFixedThreadPool (int) továrenská metóda. Výpis 1 zobrazuje implementáciu hlavnej triedy.

Zoznam 1. Konštrukcia WebCrawleru

balíček insidecoding.webcrawler; import java.util.Collection; importovať java.util.Collections; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import insidecoding.webcrawler.net.LinkFinder; import java.util.HashSet; / ** * * @author Madalin Ilie * / verejná trieda WebCrawler6 implementuje LinkHandler {súkromná finálna kolekcia navštívenáLinks = Collections.synchronizedSet (nový HashSet ()); // súkromná finálna zbierka navštívenáLinks = Collections.synchronizedList (nový ArrayList ()); súkromná reťazcová adresa URL; private ExecutorService execService; public WebCrawler6 (reťazec startingURL, int maxThreads) {this.url = startingURL; execService = Executors.newFixedThreadPool (maxThreads); } @Override public void queueLink (reťazcový odkaz) vyvolá výnimku {startNewThread (odkaz); } @Override public int size () {return navštívenáLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean navštívený (String s) {návrat navštívenýLinks.obsahuje (s); } private void startNewThread (reťazcový odkaz) vyvolá výnimku {execService.execute (nový LinkFinder (odkaz, toto)); } private void startCrawling () vyvolá výnimku {startNewThread (this.url); } / ** * @param args argumenty príkazového riadku * / public static void main (String [] args) vyvolá Exception {new WebCrawler ("// www.javaworld.com", 64) .startCrawling (); }}

Vo vyššie uvedenom WebCrawler6 konštruktora, vytvoríme fond vlákien pevnej veľkosti so 64 vláknami. Program potom spustíme zavolaním na startCrawling metóda, ktorá vytvorí prvé vlákno a odošle ho do ExecutorService.

Ďalej vytvoríme a LinkHandler rozhranie, ktoré vystavuje pomocné metódy interakcie s adresami URL. Požiadavky sú tieto: (1) označte adresu URL ako navštívenú pomocou znaku addVisited () metóda; (2) získate počet navštívených adries URL prostredníctvom servera veľkosť () metóda; (3) zistiť, či už bola adresa URL navštívená pomocou servera navštívené () metóda; a (4) pridať novú adresu URL do frontu prostredníctvom servera queueLink () metóda.

Zoznam 2. Rozhranie LinkHandler

balíček insidecoding.webcrawler; / ** * * @author Madalin Ilie * / verejné rozhranie LinkHandler {/ ** * Vloží odkaz do poradia * @param link * @throws Exception * / void queueLink (String link) vyvolá Exception; / ** * Vráti počet navštívených odkazov * @return * / int size (); / ** * Kontroluje, či už bol odkaz navštívený * @param link * @return * / boolean navštívený (reťazcový odkaz); / ** * Označí tento odkaz ako navštívený * @param link * / void addVisited (reťazcový odkaz); }

Teraz, keď prechádzame stránky, musíme naštartovať zvyšok vlákien, ktoré robíme cez LinkFinder rozhranie, ako je uvedené v zozname 3. Všimnite si linkHandler.queueLink (l) riadok.

Zoznam 3. LinkFinder

balík insidecoding.webcrawler.net; import java.net.URL; import org.htmlparser.Parser; import org.htmlparser.filters.NodeClassFilter; import org.htmlparser.tags.LinkTag; import org.htmlparser.util.NodeList; import insidecoding.webcrawler.LinkHandler; / ** * * @author Madalin Ilie * / verejná trieda LinkFinder implementuje Runnable {private String url; súkromný LinkHandler linkHandler; / ** * Použité pre štatistiku * / private static final long t0 = System.nanoTime (); public LinkFinder (String url, LinkHandler handler) {this.url = url; this.linkHandler = handler; } @Override public void run () {getSimpleLinks (url); } private void getSimpleLinks (String url) {// ak ešte nie sú navštívené if (! linkHandler.visited (url)) {try {URL uriLink = new URL (url); Parser parser = nový Parser (uriLink.openConnection ()); NodeList list = parser.extractAllNodesThatMatch (nový NodeClassFilter (LinkTag.class)); Zoznam adries URL = new ArrayList (); pre (int i = 0; i <list.size (); i ++) {LinkTag extrahovaný = (LinkTag) list.elementAt (i); if (! extrahovaný.getLink (). isEmpty () &&! linkHandler.visited (extrahovaný.getLink ())) {urls.add (extrahovaný.getLink ()); }} // navštívili sme túto adresu URL linkHandler.addVisited (url); if (linkHandler.size () == 1500) {System.out.println ("Čas navštíviť 1 500 odlišných odkazov =" + (System.nanoTime () - t0)); } for (String l: urls) {linkHandler.queueLink (l); }} catch (Výnimka e) {// zatiaľ ignorovať všetky chyby}}}}

Logika systému LinkFinder je jednoduché: (1) začneme analyzovať adresu URL; (2) potom, čo zhromaždíme všetky odkazy na príslušnej stránke, označíme stránku ako navštívenú; a (3) každý nájdený odkaz pošleme do fronty volaním súboru queueLink () metóda. Táto metóda skutočne vytvorí nové vlákno a odošle ho do ExecutorService. Ak sú v skupine k dispozícii vlákna „free“, vlákno sa vykoná; inak bude zaradený do čakacieho radu. Po dosiahnutí 1 500 rôznych navštívených odkazov vytlačíme štatistiku a program bude pokračovať.

Webový prehľadávač Java 7 s ForkJoinPool

Rámec Fork / Join zavedený v Jave 7 je vlastne implementáciou algoritmu Divide and Conquer (pozri Zdroje), v ktorom je centrálny ForkJoinPool vykoná rozvetvenie ForkJoinTasks. V tomto príklade použijeme a ForkJoinPool „zálohované“ 64 vláknami. ja hovorím cúval pretože ForkJoinTasksú ľahšie ako vlákna. Vo Vidličke / Pripojiť sa môže veľké množstvo úloh hostiť menší počet vlákien.

Podobne ako v implementácii Java 6, začneme vytvorením inštancie v WebCrawler7 konštruktér a ForkJoinPool objekt krytý 64 vláknami.

Výpis 4. Implementácia Java 7 LinkHandler

balíček insidecoding.webcrawler7; import java.util.Collection; importovať java.util.Collections; import java.util.concurrent.ForkJoinPool; import insidecoding.webcrawler7.net.LinkFinderAction; import java.util.HashSet; / ** * * @author Madalin Ilie * / verejná trieda WebCrawler7 implementuje LinkHandler {súkromná finálna kolekcia navštívenáLinks = Collections.synchronizedSet (nový HashSet ()); // súkromná finálna zbierka navštívenáLinks = Collections.synchronizedList (nový ArrayList ()); súkromná reťazcová adresa URL; private ForkJoinPool mainPool; public WebCrawler7 (reťazec startingURL, int maxThreads) {this.url = startingURL; mainPool = nový ForkJoinPool (maxThreads); } private void startCrawling () {mainPool.invoke (new LinkFinderAction (this.url, this)); } @Override public int size () {return navštívenáLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean navštívený (String s) {návrat navštívenýLinks.obsahuje (s); } / ** * @param args argumenty príkazového riadku * / public static void main (String [] args) vyvolá Exception {new WebCrawler7 ("// www.javaworld.com", 64) .startCrawling (); }}

Všimnite si, že LinkHandler rozhranie v zozname 4 je takmer rovnaké ako implementácia Java 6 zo zoznamu 2. Chýba iba queueLink () metóda. Najdôležitejšie metódy, na ktoré sa treba pozrieť, sú konštruktor a startCrawling () metóda. V konštruktore vytvoríme nový ForkJoinPool kryté 64 vláknami. (Vybral som 64 vlákien namiesto 50 alebo nejaké iné okrúhle číslo, pretože v ForkJoinPool Javadoc uvádza, že počet vlákien musí byť mocninou dvoch.) Fond vyvoláva nové LinkFinderAction, ktoré sa rekurzívne dovolajú ďalej ForkJoinTasks. Zoznam 5 zobrazuje LinkFinderAction trieda:

$config[zx-auto] not found$config[zx-overlay] not found