Knihovna Fork / Join představená v prostředí Java 7 rozšiřuje stávající balíček souběžnosti Java o podporu hardwarového paralelismu, což je klíčová vlastnost vícejádrových systémů. V tomto tipu Java Madalin Ilie demonstruje dopad výkonu nahrazení Java 6 ExecutorService
třída s Java 7 ForkJoinPool
v aplikaci webového prohledávače.
Prohledávače webu, známé také jako pavouci webu, jsou klíčem k úspěchu vyhledávačů. Tyto programy neustále skenují web, shromažďují miliony stránek dat a odesílají je zpět do databází vyhledávacích strojů. Data jsou poté indexována a zpracována algoritmicky, což vede k rychlejším a přesnějším výsledkům vyhledávání. I když se webové prohledávače nejvíce používají pro optimalizaci vyhledávání, lze je také použít pro automatizované úkoly, jako je ověřování odkazů nebo hledání a vracení konkrétních dat (například e-mailových adres) ve sbírce webových stránek.
Architektonicky je většina webových prohledávačů vysoce výkonnými vícevláknovými programy, i když s relativně jednoduchou funkčností a požadavky. Vytváření webového prohledávače je proto zajímavým způsobem, jak procvičovat a porovnávat programovací techniky s více vlákny nebo souběžně.
Návrat Java tipů!
Java Tips jsou krátké články založené na kódu, které vyzývají čtenáře JavaWorld ke sdílení jejich programovacích dovedností a objevů. Dejte nám vědět, pokud máte tip na sdílení s komunitou JavaWorld. Podívejte se také na archiv tipů Java, kde najdete další tipy na programování od vašich kolegů.
V tomto článku si projdu dva přístupy k psaní webového prohledávače: jeden používající Java 6 ExecutorService a druhý Java 7 ForkJoinPool. Abyste mohli postupovat podle příkladů, budete muset mít (od tohoto psaní) ve svém vývojovém prostředí nainstalovanou aktualizaci Java 7 update 2 a také knihovnu HtmlParser jiného výrobce.
Dva přístupy k souběžnosti Java
The ExecutorService
třída je součástí java.util.concurrent
revoluce zavedená v Javě 5 (a samozřejmě v Javě 6), která zjednodušila zpracování vláken na platformě Java. ExecutorService
je Exekutor, který poskytuje metody pro správu sledování průběhu a ukončení asynchronních úkolů. Před zavedením java.util.concurrent
, Vývojáři Java se spoléhali na knihovny třetích stran nebo psali své vlastní třídy pro správu souběžnosti ve svých programech.
Fork / Join, představený v Javě 7, nemá nahradit nebo konkurovat stávajícím třídám nástrojů souběžnosti; místo toho je aktualizuje a doplňuje. Fork / Join řeší potřebu rozděl a panuj, nebo rekurzivní zpracování úloh v programech Java (viz Zdroje).
Logika vidlice / spojení je velmi jednoduchá: (1) oddělte (vidličku) každý velký úkol na menší úkoly; (2) zpracovat každý úkol v samostatném vlákně (v případě potřeby je rozdělit na ještě menší úkoly); (3) připojit se k výsledkům.
Dvě následující implementace webového prolézacího modulu jsou jednoduché programy, které demonstrují vlastnosti a funkce prostředí Java 6 ExecutorService
a Java 7 ForkJoinPool
.
Vytváření a srovnávání webového prohledávače
Úkolem našeho webového prohledávače bude najít a sledovat odkazy. Jeho účelem může být ověření odkazu nebo shromažďování dat. (Můžete například dát programu pokyn, aby na webu prohledal obrázky Angeliny Jolie nebo Brada Pitta.)
Architektura aplikace se skládá z následujících:
- Rozhraní, které vystavuje základní operace pro interakci s odkazy; tj. získejte počet navštívených odkazů, přidejte nové odkazy, které chcete navštívit, do fronty, označte odkaz jako navštívený
- Implementace pro toto rozhraní, která bude také výchozím bodem aplikace
- Vlákno / rekurzivní akce, která bude obsahovat obchodní logiku a zkontroluje, zda již byl odkaz navštíven. Pokud ne, shromáždí všechny odkazy na příslušné stránce, vytvoří nový podproces / rekurzivní úkol a odešle jej na
ExecutorService
neboForkJoinPool
- An
ExecutorService
neboForkJoinPool
zvládnout čekající úkoly
Upozorňujeme, že odkaz je považován za „navštívený“ poté, co byly vráceny všechny odkazy na odpovídající stránce.
Kromě porovnání snadnosti vývoje pomocí nástrojů souběžnosti dostupných v prostředí Java 6 a Java 7 porovnáme výkon aplikace na základě dvou měřítek:
- Pokrytí vyhledávání: Měří čas potřebný k návštěvě 1 500 odlišný Odkazy
- Procesní výkon: Měří čas v sekundách potřebný k návštěvě 3 000 nerozlišující Odkazy; je to jako měřit, kolik kilobitů za sekundu vaše připojení k internetu zpracovává.
I když jsou poměrně jednoduché, tyto měřítka poskytují alespoň malé okno do výkonu souběžnosti prostředí Java v prostředí Java 6 versus Java 7 pro určité požadavky aplikace.
Webový prohledávač Java 6 vytvořený pomocí ExecutorService
Pro implementaci webového prolézacího modulu Java 6 použijeme fond pevných vláken se 64 vlákny, který vytvoříme voláním Executors.newFixedThreadPool (int)
tovární metoda. Výpis 1 ukazuje implementaci hlavní třídy.
Výpis 1. Konstrukce WebCrawleru
balíček insidecoding.webcrawler; import java.util.Collection; import 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 * / veřejná třída WebCrawler6 implementuje LinkHandler {soukromá finální sbírka navštívenaLinks = Collections.synchronizedSet (nový HashSet ()); // soukromá finální sbírka navštívenaLinks = Collections.synchronizedList (new ArrayList ()); soukromá adresa URL řetězce; private ExecutorService execService; public WebCrawler6 (String startingURL, int maxThreads) {this.url = startingURL; execService = Executors.newFixedThreadPool (maxThreads); } @Override public void queueLink (řetězcový odkaz) vyvolá výjimku {startNewThread (odkaz); } @Override public int size () {návrat navštívilLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean navštívil (String s) {návrat navštívilLinks.contains (s); } private void startNewThread (řetězcový odkaz) vyvolá výjimku {execService.execute (nový LinkFinder (odkaz, toto)); } private void startCrawling () vyvolá výjimku {startNewThread (this.url); } / ** * @param args argumenty příkazového řádku * / public static void main (String [] args) vyvolá Exception {new WebCrawler ("// www.javaworld.com", 64) .startCrawling (); }}
Ve výše uvedeném WebCrawler6
konstruktor, vytvoříme fond vláken pevné velikosti 64 vláken. Poté spustíme program voláním startCrawling
metoda, která vytvoří první vlákno a odešle jej do ExecutorService
.
Dále vytvoříme a LinkHandler
rozhraní, které vystavuje pomocné metody pro interakci s URL. Požadavky jsou následující: (1) označte URL jako navštívenou pomocí addVisited ()
metoda; (2) získejte počet navštívených URL prostřednictvím velikost()
metoda; (3) zjistit, zda již byla adresa URL navštívena pomocí navštíveno ()
metoda; a (4) přidat novou adresu URL do fronty prostřednictvím queueLink ()
metoda.
Výpis 2. Rozhraní LinkHandler
balíček insidecoding.webcrawler; / ** * * @author Madalin Ilie * / veřejné rozhraní LinkHandler {/ ** * Umístí odkaz do fronty * @param link * @throws Exception * / void queueLink (String link) vyvolá Exception; / ** * Vrátí počet navštívených odkazů * @return * / int size (); / ** * Zkontroluje, zda byl odkaz již navštíven * @param link * @return * / boolean navštíven (řetězec); / ** * Označí tento odkaz jako navštívený * @param link * / void addVisited (řetězcový odkaz); }
Nyní, když procházíme stránky, musíme spustit zbývající vlákna, která děláme prostřednictvím LinkFinder
rozhraní, jak je uvedeno v Seznamu 3. Všimněte si linkHandler.queueLink (l)
čára.
Výpis 3. LinkFinder
balíček 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 * / veřejná třída LinkFinder implementuje Runnable {private String url; soukromý LinkHandler linkHandler; / ** * Použité pro statistiku * / 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) {// pokud již nebyl navštíven if (! linkHandler.visited (url)) {try {URL uriLink = new URL (url); Analyzátor analyzátor = nový analyzátor (uriLink.openConnection ()); Seznam NodeList = parser.extractAllNodesThatMatch (nový NodeClassFilter (LinkTag.class)); List urls = new ArrayList (); pro (int i = 0; i <list.size (); i ++) {LinkTag extrahováno = (LinkTag) list.elementAt (i); if (! extrahovaný.getLink (). isEmpty () &&! linkHandler.visited (extrahovaný.getLink ())) {urls.add (extrahovaný.getLink ()); }} // navštívili jsme tuto adresu URL linkHandler.addVisited (url); if (linkHandler.size () == 1500) {System.out.println ("Čas návštěvy 1 500 odlišných odkazů =" + (System.nanoTime () - t0)); } for (String l: urls) {linkHandler.queueLink (l); }} catch (Výjimka e) {// zatím ignorovat všechny chyby}}}}
Logika LinkFinder
je jednoduché: (1) začneme analyzovat URL; (2) poté, co shromáždíme všechny odkazy na příslušné stránce, označíme stránku jako navštívenou; a (3) pošleme každý nalezený odkaz do fronty voláním queueLink ()
metoda. Tato metoda ve skutečnosti vytvoří nové vlákno a odešle jej do ExecutorService
. Pokud jsou ve fondu k dispozici vlákna „zdarma“, vlákno bude provedeno; jinak bude umístěn do čekající fronty. Poté, co dosáhneme 1 500 různých navštívených odkazů, vytiskneme statistiky a program pokračuje v běhu.
Webový prohledávač Java 7 s ForkJoinPool
Rámec Fork / Join představený v Javě 7 je ve skutečnosti implementací algoritmu Divide and Conquer (viz Zdroje), ve kterém je centrální ForkJoinPool
provede větvení ForkJoinTask
s. V tomto příkladu použijeme a ForkJoinPool
„zálohováno“ 64 vlákny. říkám couval protože ForkJoinTask
jsou lehčí než vlákna. Ve Fork / Join může být velký počet úkolů hostován menším počtem vláken.
Podobně jako implementace Java 6 začneme instancí v WebCrawler7
konstruktor a ForkJoinPool
objekt zajištěný 64 vlákny.
Výpis 4. Implementace Java 7 LinkHandler
balíček insidecoding.webcrawler7; import java.util.Collection; import java.util.Collections; import java.util.concurrent.ForkJoinPool; import insidecoding.webcrawler7.net.LinkFinderAction; import java.util.HashSet; / ** * * @author Madalin Ilie * / veřejná třída WebCrawler7 implementuje LinkHandler {soukromá finální sbírka navštívenaLinks = Collections.synchronizedSet (nový HashSet ()); // soukromá finální sbírka navštívenaLinks = Collections.synchronizedList (new ArrayList ()); soukromá adresa URL řetězce; private ForkJoinPool mainPool; public WebCrawler7 (String startingURL, int maxThreads) {this.url = startingURL; mainPool = new ForkJoinPool (maxThreads); } private void startCrawling () {mainPool.invoke (new LinkFinderAction (this.url, this)); } @Override public int size () {návrat navštívilLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean navštívil (String s) {návrat navštívilLinks.contains (s); } / ** * @param args argumenty příkazového řádku * / public static void main (String [] args) vyvolá Exception {new WebCrawler7 ("// www.javaworld.com", 64) .startCrawling (); }}
Všimněte si, že LinkHandler
rozhraní ve výpisu 4 je téměř stejné jako implementace Java 6 ze seznamu 2. Chybí mu pouze queueLink ()
metoda. Nejdůležitější metody, na které se podíváme, jsou konstruktor a startCrawling ()
metoda. V konstruktoru vytvoříme nový ForkJoinPool
zajištěno 64 vlákny. (Vybral jsem 64 vláken namísto 50 nebo nějaké jiné kulaté číslo, protože v ForkJoinPool
Javadoc uvádí, že počet vláken musí být mocninou dvou.) Fond vyvolá nový LinkFinderAction
, který se rekurzivně vyvolá dále ForkJoinTasks
. Výpis 5 ukazuje LinkFinderAction
třída: