Programování

Tip pro Javu: Kdy použít ForkJoinPool vs ExecutorService

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:

  1. 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ý
  2. Implementace pro toto rozhraní, která bude také výchozím bodem aplikace
  3. 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 nebo ForkJoinPool
  4. An ExecutorService nebo ForkJoinPool 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í ForkJoinTasks. V tomto příkladu použijeme a ForkJoinPool „zálohováno“ 64 vlákny. říkám couval protože ForkJoinTaskjsou 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:

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