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í:
- 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ý
- Implementácia tohto rozhrania, ktorá bude tiež východiskovým bodom aplikácie
- 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
aleboForkJoinPool
- An
ExecutorService
aleboForkJoinPool
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 ForkJoinTask
s. V tomto príklade použijeme a ForkJoinPool
„zálohované“ 64 vláknami. ja hovorím cúval pretože ForkJoinTask
sú ľ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: