Programiranje

Nasvet Java: Kdaj uporabiti ForkJoinPool vs ExecutorService

Knjižnica Fork / Join, predstavljena v Javi 7, razširja obstoječi paket sočasnosti Java s podporo za paralelnost strojne opreme, ki je ključna značilnost večjedrnih sistemov. V tem nasvetu za Java Madalin Ilie prikazuje vpliv nadomestitve Java 6 na delovanje ExecutorService razred z Javo 7 ForkJoinPool v spletnem programu za pajkanje.

Spletni pajki, znani tudi kot spletni pajki, so ključni za uspeh iskalnikov. Ti programi nenehno skenirajo splet, zberejo milijone strani podatkov in jih pošljejo nazaj v podatkovne baze iskalnikov. Podatki se nato indeksirajo in obdelajo algoritemsko, kar povzroči hitrejše in natančnejše rezultate iskanja. Medtem ko so najbolj znani za optimizacijo iskanja, lahko spletni pajki uporabljajo tudi za avtomatizirana opravila, kot so preverjanje povezav ali iskanje in vračanje določenih podatkov (kot so e-poštni naslovi) v zbirki spletnih strani.

Arhitekturno je večina spletnih pajkov visokozmogljivih večnitnih programov, čeprav s sorazmerno preprosto funkcionalnostjo in zahtevami. Izdelava spletnega pajka je zato zanimiv način vadbe, pa tudi primerjanja, večnitnih ali sočasnih tehnik programiranja.

Vrnitev nasvetov Java!

Java Tips so kratki članki na osnovi kode, ki bralce JavaWorlda vabijo, da delijo svoje programske spretnosti in odkritja. Sporočite nam, če imate namig, ki ga lahko delite s skupnostjo JavaWorld. Oglejte si tudi arhiv nasvetov Java, kjer najdete več nasvetov za programiranje vrstnikov.

V tem članku bom opisal dva pristopa k pisanju spletnega pajka: enega, ki uporablja Java 6 ExecutorService, in drugega Java 7 ForkJoinPool. Če želite slediti primerom, boste morali (v času pisanja tega dokumenta) v vašem razvojnem okolju namestiti posodobitev 2 za Java 7 in neodvisno knjižnico HtmlParser.

Dva pristopa h sočasnosti Java

The ExecutorService razred je del java.util.concurrent revolucija, uvedena v Javi 5 (in del Jave 6, seveda), ki je poenostavila obdelavo niti na platformi Java. ExecutorService je izvajalec, ki ponuja metode za upravljanje sledenja napredka in prenehanja asinhronih nalog. Pred uvedbo java.util.concurrent, Razvijalci Java so se za upravljanje sočasnosti v svojih programih zanašali na neodvisne knjižnice ali pa so napisali lastne razrede.

Fork / Join, uveden v Javi 7, ni namenjen nadomeščanju ali tekmovanju z obstoječimi razredi pripomočkov za sočasnost; namesto tega jih posodablja in dopolnjuje. Fork / Join obravnava potrebo po razdeli in osvoji, oz rekurzivno obdelava nalog v programih Java (glejte Viri).

Logika Fork / Join je zelo preprosta: (1) ločite (fork) vsako veliko nalogo na manjše naloge; (2) vsako nalogo obdela v ločeni niti (po potrebi loči na še manjše naloge); (3) pridružite se rezultatom.

Sledeči izvedbi spletnega pajka sta preprosta programa, ki prikazujeta značilnosti in funkcionalnost Java 6 ExecutorService in Java 7 ForkJoinPool.

Izdelava in primerjalna analiza spletnega pajka

Naloga našega spletnega pajka bo najti in slediti povezavam. Njegov namen je lahko preverjanje povezav ali zbiranje podatkov. (Lahko na primer programu naročite, naj po spletu išče slike Angeline Jolie ali Brada Pitta.)

Arhitektura aplikacije je sestavljena iz naslednjega:

  1. Vmesnik, ki razkriva osnovne operacije za interakcijo s povezavami; dobite število obiskanih povezav, dodajte nove povezave, ki jih želite obiskati v čakalni vrsti, označite povezavo kot obiskano
  2. Implementacija tega vmesnika, ki bo hkrati tudi izhodišče aplikacije
  3. Nit / rekurzivno dejanje, ki bo zadržalo poslovno logiko za preverjanje, ali je bila povezava že obiskana. V nasprotnem primeru bo zbral vse povezave na ustrezni strani, ustvaril novo nit / rekurzivno nalogo in jo poslal v ExecutorService ali ForkJoinPool
  4. An ExecutorService ali ForkJoinPool za izvajanje čakalnih nalog

Upoštevajte, da se povezava šteje za "obiskano", potem ko so vrnjene vse povezave na ustrezni strani.

Poleg primerjave enostavnosti razvoja z uporabo orodij za sočasnost, ki so na voljo v Java 6 in Java 7, bomo primerjali delovanje aplikacij na podlagi dveh meril uspešnosti:

  • Pokritost iskanja: Meri čas, potreben za obisk 1500 izrazit povezave
  • Procesorska moč: Meri čas v sekundah, potreben za obisk 3000 nerazločen povezave; to je kot izmeriti, koliko kilobitov na sekundo obdela vaša internetna povezava.

Čeprav so ta merila razmeroma preprosta, bodo zagotovila vsaj majhno okno za delovanje sočasnosti Java v Javi 6 v primerjavi z Javo 7 za nekatere zahteve aplikacij.

Spletni pajek Java 6, zgrajen z ExecutorService

Za izvajanje spletnega pajka Java 6 bomo uporabili področje s 64 nitmi s fiksno nitjo, ki ga ustvarimo s klicem Executors.newFixedThreadPool (int) tovarniška metoda. Seznam 1 prikazuje glavno izvedbo razreda.

Seznam 1. Izdelava WebCrawlerja

paket znotrajcoding.webcrawler; uvoz java.util.Collection; uvoz java.util.Collections; uvoz java.util.concurrent.ExecutorService; uvoz java.util.concurrent.Executors; uvoz znotrajcoding.webcrawler.net.LinkFinder; uvoz java.util.HashSet; / ** * * @author Madalin Ilie * / javni razred WebCrawler6 implementira LinkHandler {zasebna končna zbirka visitLinks = Collections.synchronizedSet (new HashSet ()); // zasebna končna zbirka visitLinks = Collections.synchronizedList (new ArrayList ()); URL zasebnega niza; private ExecutorService execService; javni WebCrawler6 (String StartURL, int maxThreads) {this.url = startURL; execService = Izvajalci.newFixedThreadPool (maxThreads); } @Override public void queueLink (String link) vrže izjemo {startNewThread (link); } @Override public int size () {vrni obiskaneLinks.size (); } @Override public void addVisited (String s) {visitLinks.add (s); } @Override javno obiskano logično ime (String s) {return visitedLinks.contains (s); } private void startNewThread (String link) vrže izjemo {execService.execute (new LinkFinder (link, this)); } private void startCrawling () vrže izjemo {startNewThread (this.url); } / ** * @param argumentira argumente ukazne vrstice * / public static void main (String [] args) vrže izjemo {new WebCrawler ("// www.javaworld.com", 64) .startCrawling (); }}

V zgornjem WebCrawler6 konstruktor, ustvarimo področje niti fiksne velikosti 64 niti. Nato program zaženemo s klicem startPlaženje metoda, ki ustvari prvo nit in jo pošlje v ExecutorService.

Nato ustvarimo a LinkHandler vmesnik, ki razkriva pomožne metode za interakcijo z URL-ji. Zahteve so naslednje: (1) URL označite kot obiskanega s pomočjo addVisited () metoda; (2) pridobite število obiskanih URL-jev prek velikost () metoda; (3) ugotovite, ali je bil URL že obiskan z uporabo obiskal () metoda; in (4) dodajte nov URL v čakalno vrsto skozi queueLink () metoda.

Seznam 2. Vmesnik LinkHandler

paket znotrajcoding.webcrawler; / ** * * @author Madalin Ilie * / javni vmesnik LinkHandler {/ ** * Povezavo postavi v čakalno vrsto * @param link * @throws Exception * / void queueLink (String link) vrže izjemo; / ** * Vrne število obiskanih povezav * @return * / int size (); / ** * Preveri, ali je bila povezava že obiskana * @param link * @return * / boolean obiskana (povezava v nizu); / ** * Označi to povezavo kot obiskano * @param link * / void addVisited (String link); }

Zdaj, ko preiskujemo strani, moramo zagnati preostale niti, kar naredimo prek LinkFinder vmesnik, kot je prikazano v seznamu 3. Upoštevajte linkHandler.queueLink (l) črta.

Seznam 3. LinkFinder

paket znotrajcoding.webcrawler.net; uvoz java.net.URL; import org.htmlparser.Parser; import org.htmlparser.filters.NodeClassFilter; import org.htmlparser.tags.LinkTag; import org.htmlparser.util.NodeList; uvoz insidecoding.webcrawler.LinkHandler; / ** * * @author Madalin Ilie * / javni razred LinkFinder izvaja Runnable {private String url; zasebno LinkHandler linkHandler; / ** * Uporabljena statistika fot * / private static final long t0 = System.nanoTime (); javni LinkFinder (String url, LinkHandler handler) {this.url = url; this.linkHandler = vodnik; } @Override public void run () {getSimpleLinks (url); } private void getSimpleLinks (String url) {// če še ni bil obiskan if (! linkHandler.visited (url)) {try {URL uriLink = new URL (url); Razčlenjevalnik razčlenjevalnik = nov razčlenjevalnik (uriLink.openConnection ()); Seznam vozlišč = parser.extractAllNodesThatMatch (nov NodeClassFilter (LinkTag.class)); URL-ji seznamov = new ArrayList (); for (int i = 0; i <list.size (); i ++) {LinkTag extracted = (LinkTag) list.elementAt (i); if (! extracted.getLink (). isEmpty () &&! linkHandler.visited (extracted.getLink ())) {urls.add (extracted.getLink ()); }} // obiskali smo ta url linkHandler.addVisited (url); if (linkHandler.size () == 1500) {System.out.println ("Čas za obisk 1500 različnih povezav =" + (System.nanoTime () - t0)); } za (String l: urls) {linkHandler.queueLink (l); }} catch (izjema e) {// za zdaj prezri vse napake}}}}

Logika LinkFinder je preprosto: (1) začnemo razčlenjevati URL; (2) ko zberemo vse povezave na ustrezni strani, stran označimo kot obiskano; in (3) vsako najdeno povezavo pošljemo v čakalno vrsto s klicem na queueLink () metoda. Ta metoda bo dejansko ustvarila novo nit in jo poslala v ExecutorService. Če so v področju na voljo "brezplačne" niti, bo nit izvedena; v nasprotnem primeru bo postavljen v čakalno vrsto. Ko dosežemo 1500 različnih obiskanih povezav, natisnemo statistične podatke in program se nadaljuje.

Spletni pajek Java 7 s programom ForkJoinPool

Okvir Fork / Join, uveden v Javi 7, je dejansko izvedba algoritma Divide and Conquer (glej Viri), v katerem je osrednji ForkJoinPool izvede razvejanje ForkJoinTasks. Za ta primer bomo uporabili a ForkJoinPool "podprto" s 64 nitmi. pravim podprta Ker ForkJoinTaskso lažji od niti. V Fork / Join lahko veliko število nalog gosti manjše število niti.

Podobno kot pri izvajanju Java 6, začnemo z instanciranjem v WebCrawler7 konstruktor a ForkJoinPool predmet, podprt s 64 nitmi.

Seznam 4. Izvajanje Java 7 LinkHandler

paket znotrajcoding.webcrawler7; uvoz java.util.Collection; uvoz java.util.Collections; uvoz java.util.concurrent.ForkJoinPool; uvoz insidecoding.webcrawler7.net.LinkFinderAction; uvoz java.util.HashSet; / ** * * @author Madalin Ilie * / javni razred WebCrawler7 implementira LinkHandler {zasebna končna zbirka visitLinks = Collections.synchronizedSet (new HashSet ()); // zasebna končna zbirka visitLinks = Collections.synchronizedList (new ArrayList ()); URL zasebnega niza; zasebni ForkJoinPool mainPool; public WebCrawler7 (String StartURL, int maxThreads) {this.url = startURL; mainPool = nov ForkJoinPool (maxThreads); } private void startCrawling () {mainPool.invoke (new LinkFinderAction (this.url, this)); } @Override public int size () {vrni obiskane povezave.size (); } @Override public void addVisited (String s) {obiskaniLinks.add (s); } @Override javno logično obiskano (String s) {return visitedLinks.contains (s); } / ** * @param argumentira argumente ukazne vrstice * / public static void main (String [] args) vrže izjemo {new WebCrawler7 ("// www.javaworld.com", 64) .startCrawling (); }}

Upoštevajte, da LinkHandler vmesnik v seznamu 4 je skoraj enak izvajanju Java 6 iz seznama 2. Manjka le queueLink () metoda. Najpomembnejši metodi, ki si ju je treba ogledati, sta konstruktor in startCrawling () metoda. V konstruktorju ustvarimo novo ForkJoinPool podprto s 64 nitmi. (Izbral sem 64 niti namesto 50 ali katero drugo okroglo številko, ker je v ForkJoinPool Javadoc navaja, da mora biti število niti dve stopnji.) Skupina prikliče novo LinkFinderAction, ki se bo rekurzivno skliceval naprej ForkJoinTasks. Seznam 5 prikazuje LinkFinderAction razred:

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