Programmēšana

Java padoms: kad izmantot ForkJoinPool vs ExecutorService

Fork / Join bibliotēka, kas ieviesta Java 7, paplašina esošo Java vienlaicīguma paketi, atbalstot aparatūras paralēlismu, kas ir daudzkodolu sistēmu galvenā iezīme. Šajā Java Padomā Madalin Ilie parāda Java 6 nomaiņas ietekmi uz veiktspēju ExecutorService klase ar Java 7 ForkJoinPool tīmekļa rāpuļprogrammas lietojumprogrammā.

Tīmekļa rāpuļprogrammas, kas pazīstamas arī kā tīmekļa zirnekļi, ir meklētājprogrammu panākumu atslēga. Šīs programmas pastāvīgi skenē tīmekli, apkopojot miljoniem datu lapu un nosūtot tos atpakaļ meklētājprogrammu datu bāzēs. Pēc tam dati tiek indeksēti un apstrādāti algoritmiski, kā rezultātā tiek iegūti ātrāki un precīzāki meklēšanas rezultāti. Lai gan tos visvairāk izmanto meklēšanas optimizēšanai, tīmekļa rāpuļprogrammas var izmantot arī automatizētiem uzdevumiem, piemēram, saišu validācijai vai konkrētu datu (piemēram, e-pasta adrešu) atrašanai un atgriešanai tīmekļa lapu kolekcijā.

Arhitektoniski lielākā daļa tīmekļa rāpuļprogrammu ir augstas veiktspējas daudzlīniju programmas, kaut arī ar salīdzinoši vienkāršu funkcionalitāti un prasībām. Tāpēc tīmekļa rāpuļprogrammas izveide ir interesants veids, kā praktizēt, kā arī salīdzināt, daudzšķiedru vai vienlaicīgas programmēšanas metodes.

Java padomu atgriešanās!

Java padomi ir īsi, uz kodu balstīti raksti, kas aicina JavaWorld lasītājus dalīties programmēšanas prasmēs un atklājumos. Informējiet mūs, ja jums ir padoms, ko dalīties ar JavaWorld kopienu. Apskatiet arī Java padomu arhīvu, lai iegūtu vairāk vienaudžu programmēšanas padomu.

Šajā rakstā es apskatīšu divas pieejas tīmekļa rāpuļa rakstīšanai: viena, izmantojot Java 6 ExecutorService, un otra Java 7 ForkJoinPool. Lai sekotu piemēriem, izstrādes vidē jums būs jābūt instalētam (šī raksta laikā) Java 7 2. atjauninājumam, kā arī trešās puses bibliotēkai HtmlParser.

Divas pieejas Java vienlaicīgumam

The ExecutorService klase ir daļa no java.util.concurrent revolūcija, kas ieviesta Java 5 (un, protams, daļa no Java 6), kas vienkāršoja pavedienu apstrādi Java platformā. ExecutorService ir izpildītājs, kas nodrošina metodes, kā pārvaldīt progresa izsekošanu un asinhrono uzdevumu pārtraukšanu. Pirms java.util.concurrent, Java izstrādātāji paļāvās uz trešo pušu bibliotēkām vai uzrakstīja savas klases, lai pārvaldītu viņu programmu vienlaicīgumu.

Fork / Join, kas ieviests Java 7, nav paredzēts, lai aizstātu esošās vienlaicīguma lietderības klases vai konkurētu ar tām; tā vietā tos atjaunina un pabeidz. Ar dakšu / savienojumu tiek novērsta nepieciešamība sadalīt un iekarot vai rekursīvs uzdevumu apstrāde Java programmās (skatiet resursus).

Fork / Join loģika ir ļoti vienkārša: (1) katru lielu uzdevumu atdaliet (dakšiņu) mazākos uzdevumos; (2) apstrādājiet katru uzdevumu atsevišķā pavedienā (vajadzības gadījumā tos sadalot vēl mazākos uzdevumos); (3) pievienoties rezultātiem.

Divas sekojošās tīmekļa rāpuļprogrammas ieviešanas ir vienkāršas programmas, kas parāda Java 6 funkcijas un funkcionalitāti ExecutorService un Java 7 ForkJoinPool.

Tīmekļa rāpuļprogrammas izveide un salīdzinošā novērtēšana

Mūsu tīmekļa rāpuļprogrammas uzdevums būs atrast un sekot saitēm. Tās mērķis varētu būt saites validācija vai datu vākšana. (Jūs, piemēram, varat uzdot programmai meklēt tīmeklī Andželīnas Džolijas vai Breda Pita attēlus.)

Lietojumprogrammas arhitektūru veido šādi:

  1. Saskarne, kas atklāj pamatdarbības mijiedarbībai ar saitēm; i., iegūstiet apmeklēto saišu skaitu, pievienojiet jaunas saites, kas jāapmeklē rindā, atzīmējiet saiti kā apmeklētu
  2. Šīs saskarnes ieviešana, kas būs arī lietojumprogrammas sākumpunkts
  3. Vītne / rekursīva darbība, kas satur biznesa loģiku, lai pārbaudītu, vai saite jau ir apmeklēta. Ja nē, tas apkopos visas saites attiecīgajā lapā, izveidos jaunu pavedienu / rekursīvu uzdevumu un iesniegs to ExecutorService vai ForkJoinPool
  4. An ExecutorService vai ForkJoinPool rīkoties ar gaidīšanas uzdevumiem

Ņemiet vērā, ka saite tiek uzskatīta par apmeklētu pēc tam, kad visas saites attiecīgajā lapā ir atgrieztas.

Papildus izstrādes vienkāršības salīdzināšanai, izmantojot Java 6 un Java 7 pieejamos vienlaicīguma rīkus, mēs salīdzināsim lietojumprogrammu veiktspēju, pamatojoties uz diviem kritērijiem:

  • Meklēšanas pārklājums: Mēra laiku, kas nepieciešams, lai apmeklētu 1500 atšķirīgs saites
  • Apstrādes jauda: Mēra laiku sekundēs, kas nepieciešams, lai apmeklētu 3000 nav atšķirīgs saites; tas ir tāpat kā mērīt, cik kilobitu sekundē apstrādā jūsu interneta savienojums.

Kaut arī salīdzinoši vienkārši, šie kritēriji nodrošinās vismaz nelielu logu Java vienlaicīguma veiktspējai Java 6 un Java 7 noteiktām lietojumprogrammu prasībām.

Java 6 tīmekļa rāpuļprogramma, kas izveidota, izmantojot ExecutorService

Java 6 tīmekļa rāpuļprogrammas ieviešanai mēs izmantosim fiksēto pavedienu kopu no 64 pavedieniem, kurus mēs izveidojam, izsaucot Executors.newFixedThreadPool (int) rūpnīcas metode. 1. saraksts parāda galveno klases ieviešanu.

Uzskaitīšana 1. WebRāpuļa izveidošana

pakotne insidecoding.webcrawler; importēt java.util.Collection; importēt java.util.Collections; importēt java.util.concurrent.ExecutorService; importēt java.util.concurrent.Executors; importēt insidecoding.webcrawler.net.LinkFinder; importēt java.util.HashSet; / ** * * @author Madalin Ilie * / public class WebCrawler6 īsteno LinkHandler {privātā galīgā kolekcija visitLinks = Collections.synchronizedSet (new HashSet ()); // privātā galīgā kolekcija visitLinks = Collections.synchronizedList (new ArrayList ()); privāts virknes URL; privāts ExecutorService execService; public WebCrawler6 (virkne sākasURL, int maxThreads) {this.url = sākumaURL; execService = Executors.newFixedThreadPool (maxThreads); } @Orride public void queueLink (virknes saite) met izņēmumu {startNewThread (saite); } @ Pārvarēt publiskā int izmēru () {atgriezties visitLinks.size (); } @Orride public void addVisited (String s) {visitLinks.add (s); } @ Pārvarēt apmeklēto publisko būla vērtību (virkne) {atgriešanās visitLinks.contains (s); } private void startNewThread (virknes saite) izmet izņēmumu {execService.execute (new LinkFinder (link, this)); } private void startCrawling () izmet izņēmumu {startNewThread (this.url); } / ** * @param argumentē komandrindas argumentus * / public static void main (String [] args) izmet izņēmumu {new WebCrawler ("// www.javaworld.com", 64) .startCrawling (); }}

Iepriekš minētajā Tīmekļa pārmeklētājs6 konstruktors, mēs izveidojam fiksēta izmēra pavedienu kopu no 64 pavedieniem. Pēc tam mēs sākam programmu, zvanot uz startRāpošana metodi, kas izveido pirmo pavedienu un iesniedz to ExecutorService.

Tālāk mēs izveidojam a LinkHandler saskarne, kas parāda palīgu metodes mijiedarbībai ar URL. Prasības ir šādas: (1) atzīmējiet URL kā apmeklētu, izmantojot addVisited () metode; (2) iegūt apmeklēto vietrāžu URL skaitu, izmantojot Izmērs() metode; (3) noteikt, vai URL jau ir apmeklēts, izmantojot vietni apmeklēja () metode; un (4) pievienojiet jaunu URL rindā, izmantojot queueLink () metodi.

Saraksts 2. LinkHandler interfeiss

pakotne insidecoding.webcrawler; / ** * * @autors Madalin Ilie * / publiskais interfeiss LinkHandler {/ ** * saiti ievieto rindā * @param link * @throws Exception * / void queueLink (virknes saite) izmet izņēmumu; / ** * Atgriež apmeklēto saišu skaitu * @return * / int size (); / ** * Pārbauda, ​​vai saite jau ir apmeklēta * @param saite * @return * / Būla apmeklēta (virknes saite); / ** * Atzīmē šo saiti kā apmeklētu * @param link * / void addVisited (virknes saite); }

Tagad, kad mēs pārmeklējam lapas, mums jāuzsāk pārējie pavedieni, ko mēs darām, izmantojot LinkFinder saskarni, kā parādīts 3. sarakstā. Ievērojiet linkHandler.queueLink (l) līnija.

Saraksts 3. LinkFinder

pakete insidecoding.webcrawler.net; importēt java.net.URL; importēt org.htmlparser.Parser; importēt org.htmlparser.filters.NodeClassFilter; importēt org.htmlparser.tags.LinkTag; importēt org.htmlparser.util.NodeList; importēt insidecoding.webcrawler.LinkHandler; / ** * * @autors Madalin Ilie * / publiskā klase LinkFinder īsteno Runnable {private String url; privāts LinkHandler linkHandler; / ** * Izmantotā fot statistika * / privātā statiskā galīgā garā t0 = System.nanoTime (); publiskais LinkFinder (virknes URL, LinkHandler apdarinātājs) {this.url = url; this.linkHandler = apdarinātājs; } @Orride public void run () {getSimpleLinks (url); } private void getSimpleLinks (virknes URL) {// ja vēl nav apmeklēts, ja (! linkHandler.visited (url)) {mēģiniet {URL uriLink = jauns URL (url); Parsētāja parsētājs = jauns parsētājs (uriLink.openConnection ()); NodeList saraksts = parser.extractAllNodesThatMatch (jauns NodeClassFilter (LinkTag.class)); Saraksta URL = 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 (extract.getLink ()); }} // mēs apmeklējām šo URL saitiHandler.addVisited (url); if (linkHandler.size () == 1500) {System.out.println ("Laiks apmeklēt 1500 atšķirīgas saites =" + (System.nanoTime () - t0)); } par (String l: urls) {linkHandler.queueLink (l); }} catch (izņēmums e) {// pagaidām ignorējiet visas kļūdas}}}}

Programmas loģika LinkFinder ir vienkāršs: (1) mēs sākam parsēt URL; (2) pēc tam, kad esam savākuši visas saites attiecīgajā lapā, mēs atzīmējam lapu kā apmeklētu; un (3) mēs katru atrasto saiti nosūtām uz rindu, zvanot uz queueLink () metodi. Šī metode faktiski izveidos jaunu pavedienu un nosūtīs to ExecutorService. Ja baseinā ir pieejami "bezmaksas" pavedieni, pavediens tiks izpildīts; pretējā gadījumā tas tiks ievietots gaidīšanas rindā. Kad esam sasnieguši 1500 atšķirīgas apmeklētās saites, mēs izdrukājam statistiku, un programma turpina darboties.

Java 7 tīmekļa rāpuļprogramma ar ForkJoinPool

Fork / Join sistēma, kas ieviesta Java 7, faktiski ir sadalīšanas un iekarošanas algoritma ieviešana (sk. Resursus), kurā centrālais ForkJoinPool veic atzarošanu ForkJoinTasks. Šajā piemērā mēs izmantosim a ForkJoinPool "atbalstīts" ar 64 pavedieniem. Es saku atbalstīja jo ForkJoinTasks ir vieglāki par diegiem. Programmā Fork / Join lielu skaitu uzdevumu var mitināt ar mazāku pavedienu skaitu.

Līdzīgi kā Java 6 ieviešana, mēs sākam ar instancēšanu Webrāpuļprogramma7 konstruktors a ForkJoinPool objekts, kuru atbalsta 64 pavedieni.

Saraksts 4. Java 7 LinkHandler ieviešana

pakotne insidecoding.webcrawler7; importēt java.util.Collection; importēt java.util.Collections; importēt java.util.concurrent.ForkJoinPool; importēt insidecoding.webcrawler7.net.LinkFinderAction; importēt java.util.HashSet; / ** * * @author Madalin Ilie * / public class WebCrawler7 īsteno LinkHandler {privātā galīgā kolekcija visitLinks = Collections.synchronizedSet (new HashSet ()); // privātā galīgā kolekcija visitLinks = Collections.synchronizedList (new ArrayList ()); privāts virknes URL; privāts ForkJoinPool mainPool; public WebCrawler7 (virkne sākumaURL, int maxThreads) {this.url = sākumaURL; mainPool = jauns ForkJoinPool (maxThreads); } private void startCrawling () {mainPool.invoke (new LinkFinderAction (this.url, this)); } @Orride public int size () {atgriezties visitLinks.size (); } @Orride public void addVisited (String s) {visitLinks.add (s); } @ Pārvarēt apmeklēto publisko būla vērtību (virkne) {atgriešanās visitLinks.contains (s); } / ** * @param argumentē komandrindas argumentus * / public static void main (String [] args) met izņēmumu {new WebCrawler7 ("// www.javaworld.com", 64) .startCrawling (); }}

Ņemiet vērā, ka LinkHandler 4. saraksta saskarne ir gandrīz tāda pati kā Java 6 ieviešana no 2. saraksta. Tam trūkst tikai queueLink () metodi. Vissvarīgākās apskatāmās metodes ir konstruktors un startCrawling () metodi. Konstruktorā mēs izveidojam jaunu ForkJoinPool ar 64 pavedieniem. (Esmu izvēlējies 64 pavedienus, nevis 50 vai kādu citu apaļu skaitli, jo ForkJoinPool Džavadokā teikts, ka pavedienu skaitam jābūt divu jaudai.) Pūlis izsauc jaunu LinkFinderAction, kas rekursīvi atsauksies tālāk ForkJoinTasks. 5. saraksts parāda LinkFinderAction klase: