Ohjelmointi

Java-vinkki: Milloin ForkJoinPool vs ExecutorService käytetään

Java 7: ssä esitelty Fork / Join-kirjasto laajentaa olemassa olevaa Java-samanaikaisuuspakettia tukemalla laitteiston rinnakkaisuutta, joka on moniydinjärjestelmien keskeinen piirre. Tässä Java-vinkissä Madalin Ilie osoittaa Java 6: n korvaamisen vaikutuksen suorituskykyyn ExecutorService luokka Java 7: n kanssa HaarukkaLiity uima-allas indeksointisovelluksessa.

Web-indeksoijat, jotka tunnetaan myös nimellä web-hämähäkit, ovat avain hakukoneiden menestykseen. Nämä ohjelmat skannaavat jatkuvasti verkkoa, keräävät miljoonia sivuja tietoja ja lähettävät sen takaisin hakukoneiden tietokantoihin. Tiedot indeksoidaan ja käsitellään algoritmisesti, mikä johtaa nopeampiin ja tarkempiin hakutuloksiin. Vaikka web-indeksoijia käytetään tunnetuimmin hakujen optimointiin, niitä voidaan käyttää myös automatisoituihin tehtäviin, kuten linkkien validointiin tai tiettyjen tietojen (kuten sähköpostiosoitteiden) löytämiseen ja palauttamiseen verkkosivujen kokoelmassa.

Arkkitehtonisesti useimmat indeksointirobotit ovat tehokkaita monisäikeisiä ohjelmia, vaikkakin suhteellisen yksinkertaisilla toiminnoilla ja vaatimuksilla. Web-indeksoijan rakentaminen on siis mielenkiintoinen tapa harjoitella sekä verrata, monisäikeisiä tai samanaikaisia ​​ohjelmointitekniikoita.

Java-vinkkien paluu!

Java-vinkit ovat lyhyitä, koodipohjaisia ​​artikkeleita, jotka kutsuvat JavaWorld-lukijoita jakamaan ohjelmointitaitojaan ja löytöjään. Kerro meille, jos sinulla on vinkki, jonka voit jakaa JavaWorld-yhteisölle. Katso myös Java-vinkit-arkistosta lisää ohjelmointivinkkejä ikäisiltäsi.

Tässä artikkelissa käyn läpi kaksi tapaa kirjoittaa web-indeksoija: toinen käyttää Java 6 ExecutorServicea ja toinen Java 7: n ForkJoinPool. Jotta voit seurata esimerkkejä, sinun on (tämän kirjoituksen jälkeen) asennettava Java 7 -päivitys 2 kehitysympäristösi sekä kolmannen osapuolen kirjasto HtmlParser.

Kaksi lähestymistapaa Java-samanaikaisuuteen

ExecutorService luokka on osa java.util.concurrent Java 5: ssä (ja tietysti osassa Java 6) käyttöön otettu vallankumous, joka yksinkertaisti langankäsittelyä Java-alustalla. ExecutorService on suorittaja, joka tarjoaa menetelmiä asynkronisten tehtävien etenemisen seurannan ja lopettamisen hallitsemiseksi. Ennen java.util.concurrent, Java-kehittäjät luottivat kolmansien osapuolten kirjastoihin tai kirjoittivat omat luokkansa hallitakseen samanaikaisuutta ohjelmissaan.

Java 7: ssä esitetyn haarukan / liittymisen ei ole tarkoitus korvata olemassa olevia samanaikaisuustyökaluluokkia tai kilpailla niiden kanssa; sen sijaan se päivittää ja täydentää niitä. Haarukka / Liity -toiminto korjaa jakamisen ja valloittamisen tarpeen rekursiivinen tehtävien käsittely Java-ohjelmissa (katso Resurssit).

Fork / Join-logiikka on hyvin yksinkertainen: (1) erota (haarukka) jokainen suuri tehtävä pienempiin tehtäviin; (2) käsittele kukin tehtävä erillisessä säikeessä (erottamalla ne tarvittaessa vielä pienempiin tehtäviin); (3) liittyä tuloksiin.

Kaksi seuraavaa web-indeksointiratkaisua ovat yksinkertaisia ​​ohjelmia, jotka esittävät Java 6: n ominaisuuksia ja toiminnallisuutta ExecutorService ja Java 7 HaarukkaLiity uima-allas.

Web-indeksoijan rakentaminen ja vertailu

Indeksointirobottimme tehtävänä on löytää ja seurata linkkejä. Sen tarkoitus voi olla linkin vahvistus tai tietojen kerääminen. (Voit esimerkiksi kehottaa ohjelmaa etsimään verkosta kuvia Angelina Jolieesta tai Brad Pittistä.)

Sovellusarkkitehtuuri koostuu seuraavista:

  1. Rajapinta, joka paljastaa perustoiminnot vuorovaikutukseen linkkien kanssa; ts. hanki vierailtujen linkkien lukumäärä, lisää uusia linkkejä jonossa vierailuun, merkitse linkki käydyksi
  2. Tämän käyttöliittymän toteutus, joka on myös sovelluksen lähtökohta
  3. Lanka / rekursiivinen toiminto, joka pitää liikelogiikkaa tarkistaakseen, onko linkissä jo käynyt. Jos ei, se kerää kaikki linkit vastaavalle sivulle, luo uuden ketjun / rekursiivisen tehtävän ja lähettää sen ExecutorService tai HaarukkaLiity uima-allas
  4. An ExecutorService tai HaarukkaLiity uima-allas hoitamaan odottavia tehtäviä

Huomaa, että linkkiä pidetään "käydynä", kun kaikki vastaavan sivun linkit on palautettu.

Sen lisäksi, että verrataan kehityksen helppoutta Java 6: ssa ja Java 7: ssä olevien samanaikaisuustyökalujen avulla, vertaamme sovellusten suorituskykyä kahden vertailuarvon perusteella:

  • Haun kattavuus: Mittaa 1500 käyntiin tarvittavan ajan erillinen linkkejä
  • Prosessiteho: Mittaa 3000: n käyntiin tarvittavan ajan sekunneissa ei-erottuva linkit; tämä on kuin mitata kuinka monta kilobittiä sekunnissa Internet-yhteys käsittelee.

Vaikka nämä vertailuarvot ovatkin melko yksinkertaisia, ne tarjoavat ainakin pienen ikkunan Java-samanaikaisuuden suorituskykyyn Java 6: ssa verrattuna Java 7: een tietyille sovellusvaatimuksille.

ExecutorService-palvelimella rakennettu Java 6 -verkkorobotti

Java 6: n web-indeksointirobottien toteutuksessa käytämme kiinteän ketjun 64 ketjun ryhmää, jotka luomme kutsumalla Executors.newFixedThreadPool (int) tehtaan menetelmä. Listaus 1 näyttää pääluokan toteutuksen.

Luettelointi 1. Web-indeksoijan rakentaminen

paketin insidecoding.webcrawler; tuo java.util.Collection; tuo java.util.Collections; tuo java.util.concurrent.ExecutorService; tuo java.util.concurrent.Executors; tuoda insidecoding.webcrawler.net.LinkFinder; tuo java.util.HashSet; / ** * * @author Madalin Ilie * / public class WebCrawler6 toteuttaa LinkHandler {private final Collection visitLinks = Collections.synchronizedSet (uusi HashSet ()); // yksityinen lopullinen kokoelma visitLinks = Collections.synchronizedList (uusi ArrayList ()); yksityinen merkkijono-URL; yksityinen ExecutorService execService; public WebCrawler6 (Merkkijono alkaaURL, int maxThreads) {this.url = alkaaURL; execService = Executors.newFixedThreadPool (maxThreads); } @Override public void queueLink (String link) heittää poikkeuksen {startNewThread (linkki); } @Override public int size () {return visitLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean käynyt (merkkijonot) {return visitLinks.contains (s); } private void startNewThread (merkkijonolinkki) heittää poikkeuksen {execService.execute (uusi LinkFinder (linkki, tämä)); } private void startCrawling () heittää poikkeuksen {startNewThread (this.url); } / ** * @param argumentoi komentoriviargumentit * / public static void main (String [] args) heittää poikkeuksen {new WebCrawler ("// www.javaworld.com", 64) .startCrawling (); }}

Edellä WebCrawler 6 rakentaja, luomme kiinteän koon 64-säikeisen langan. Aloitamme sitten ohjelman soittamalla startCrawling menetelmä, joka luo ensimmäisen ketjun ja lähettää sen ExecutorService.

Seuraavaksi luomme LinkHandler käyttöliittymä, joka paljastaa auttajamenetelmät vuorovaikutuksessa URL-osoitteiden kanssa. Vaatimukset ovat seuraavat: (1) merkitse URL-osoite vierailetuksi käyttämällä addVisited () menetelmä; (2) hanki vierailtujen URL-osoitteiden määrä koko() menetelmä; (3) määrittää, onko URL-osoitteessa jo käynyt URL-osoitteen avulla vieraili () menetelmä; ja (4) lisätään uusi URL jonoon queueLink () menetelmä.

Listaus 2. LinkHandler-käyttöliittymä

paketin insidecoding.webcrawler; / ** * * @author Madalin Ilie * / julkinen käyttöliittymä LinkHandler {/ ** * Sijoittaa linkin jonoon * @param -linkki * @throws Exception * / void queueLink (String link) heittää Exception; / ** * Palauttaa vierailtujen linkkien lukumäärän * @return * / int size (); / ** * tarkistaa, onko linkissä jo käynyt * @param-linkki * @return * / looginen vierailu (merkkijonolinkki); / ** * Merkitsee tämän linkin vierailuksi * @param -linkki * / void addVisited (merkkijonolinkki); }

Kun indeksoimme sivuja, meidän on käynnistettävä loput säikeet, jotka teemme LinkFinder käyttöliittymä, kuten luettelossa 3. näkyy. Huomaa linkHandler.queueLink (l) linja.

Listaus 3. LinkFinder

paketti insidecoding.webcrawler.net; tuo java.net.URL; tuo org.htmlparser.Parser; tuo org.htmlparser.filters.NodeClassFilter; tuo org.htmlparser.tags.LinkTag; tuo org.htmlparser.util.NodeList; tuoda insidecoding.webcrawler.LinkHandler; / ** * * @author Madalin Ilie * / public class LinkFinder toteuttaa Runnable {private String url; yksityinen LinkHandler linkHandler; / ** * Käytetyt fototilastot * / yksityinen staattinen lopullinen pitkä t0 = System.nanoTime (); public LinkFinder (String url, LinkHandler handler) {this.url = url; this.linkHandler = käsittelijä; } @Override public void run () {getSimpleLinks (url); } private void getSimpleLinks (String url) {// ellei sitä vielä ole käynyt, jos (! linkHandler.visited (url)) {kokeile {URL uriLink = uusi URL (url); Parser parser = uusi jäsennin (uriLink.openConnection ()); NodeList list = parser.extractAllNodesThatMatch (uusi NodeClassFilter (LinkTag.class)); Luettelon URL-osoitteet = new ArrayList (); for (int i = 0; i <list.size (); i ++) {LinkTag purettu = (LinkTag) list.elementAt (i); if (! extracted.getLink (). isEmpty () &&! linkHandler.visited (extracted.getLink ())) {urls.add (extract.getLink ()); }} // vierailimme tässä URL-linkissäHandler.addVisited (url); if (linkHandler.size () == 1500) {System.out.println ("Aika vierailla 1500 erillisessä linkissä =" + (System.nanoTime () - t0)); } (merkkijono l: urls) {linkHandler.queueLink (l); }} catch (Poikkeus e) {// ohita kaikki virheet toistaiseksi}}}}

- logiikka LinkFinder on yksinkertainen: (1) aloitamme URL-osoitteen jäsentämisen; (2) Kun olemme keränneet kaikki linkit vastaavalle sivulle, merkitsemme sivun käydyksi; ja (3) lähetämme jokaisen löydetyn linkin jonoon soittamalla queueLink () menetelmä. Tämä menetelmä luo itse asiassa uuden ketjun ja lähettää sen ExecutorService. Jos poolissa on käytettävissä "ilmaisia" ketjuja, ketju suoritetaan; muuten se asetetaan odotusjonoon. Kun olemme saavuttaneet 1 500 erillistä vierailtua linkkiä, tulostamme tilastotiedot ja ohjelma jatkuu.

Java 7 -verkkorobotti, jossa on ForkJoinPool

Java 7: ssä esitetty Fork / Join-kehys on itse asiassa Divide and Conquer -algoritmin toteutus (katso Resurssit), jossa keskeinen HaarukkaLiity uima-allas suorittaa haarautumisen HaarukkaLiity Tehtäväs. Tässä esimerkissä käytämme a HaarukkaLiity uima-allas "tukee" 64 säiettä. minä sanon tukena koska HaarukkaLiity Tehtäväs ovat kevyempiä kuin langat. Fork / Join -palvelussa suuri määrä tehtäviä voidaan isännöidä pienemmällä määrällä säikeitä.

Samanlainen kuin Java 6 -toteutus, aloitamme instantisoimalla WebCrawler 7 rakentaja a HaarukkaLiity uima-allas esine, jota tukee 64 säiettä.

Listaus 4. Java 7 LinkHandler -toteutus

paketti insidecoding.webcrawler7; tuo java.util.Collection; tuo java.util.Collections; tuo java.util.concurrent.ForkJoinPool; tuoda insidecoding.webcrawler7.net.LinkFinderAction; tuo java.util.HashSet; / ** * * @author Madalin Ilie * / public class WebCrawler7 toteuttaa LinkHandler {private final Collection visitLinks = Collections.synchronizedSet (uusi HashSet ()); // yksityinen lopullinen kokoelma visitLinks = Collections.synchronizedList (uusi ArrayList ()); yksityinen merkkijono-URL; yksityinen ForkJoinPool mainPool; public WebCrawler7 (Merkkijono alkaaURL, int maxThreads) {this.url = alkaaURL; mainPool = uusi ForkJoinPool (maxThreads); } private void startCrawling () {mainPool.invoke (uusi LinkFinderAction (this.url, this)); } @Override public int size () {return visitLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override public boolean käynyt (merkkijonot) {return visitLinks.contains (s); } / ** * @param argumentoi komentoriviargumentit * / public static void main (String [] args) heittää poikkeuksen {new WebCrawler7 ("// www.javaworld.com", 64) .startCrawling (); }}

Huomaa, että LinkHandler Listing 4: n käyttöliittymä on melkein sama kuin Java 6 -toteutus Listing 2: sta. Sieltä puuttuu vain queueLink () menetelmä. Tärkeimmät tarkasteltavat menetelmät ovat konstruktori ja startCrawling () menetelmä. Rakentajana luomme uuden HaarukkaLiity uima-allas jota tukee 64 säiettä. (Olen valinnut 64 säiettä 50 tai jonkin muun pyöreän numeron sijaan, koska HaarukkaLiity uima-allas Javadoc siinä todetaan, että ketjujen lukumäärän on oltava kahden voima.) Allas kutsuu uutta LinkFinderAction, joka rekursiivisesti vetoaa edelleen HaarukkaLiity Tehtävät. Luettelossa 5 näkyy LinkFinderAction luokka: