ABONAMENTE VIDEO REDACȚIA
RO
EN
Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 24
Abonament PDF

Java 8 – Procesarea colecțiilor

Ovidiu Simionica
Team Lead
@Fortech



PROGRAMARE

Java 8 e frumoasă. Da, îi atribui oarecum genul feminin încă dinainte de a ajunge la magica cifra 8. În acest articol mă aventurez în a analiza în ce măsură această frumusețe este formă versus fond. Admit că de-a lungul competiției limbajelor de programare, Java a tot rămas datoare în a-și satisface comunitatea de entuziaști.

Lambda expressions. Da, C# avea Lambda expressions de ceva vreme, mai precis din versiunea 3.0 din anul 2007. Java a mai avut nevoie de încă șapte ani. Functional programming atât de târziu.

Generics. O amăgire în forma către template metaprogramming eșuând prin type erasure sau doar eu speram la altceva?

Mă simt ca un iubitor de Nokia ce visează la high resolution și la Android. Ca tot javrar-ul, vă imaginați cum așteptam forțând butonul de refresh de pe site-ul Oracle pentru a da o descărcare la proaspăta versiune în ziua release-ului.

Aflându-se, la data articolului de față, de ceva vreme pe "masa de operații", iată că am ajuns acum să împărtășesc impresii cu privire la o chestiune ce m-a tot frământat de-a lungul carierei: prelucrarea colecțiilor de date.

Ce este prelucrarea colecțiilor de date (aka filtrarea)?

Pentru cei familiarizați cu SQL, filtrarea este o operație de bază asupra unui set de date de tipul:

SELECT * FROM Cars WHERE 
Cars.manufacturer = "VW";

Dar ce ne facem dacă colecția este deja în memoria programului și vrem să o prelucrăm conform condițiilor de mai sus?

Până la Java 8, programatorul scria:

ArrayList filteredCars = 
  new ArrayList();
 
for(Car c : allCars) {
  if ("VW".equals(
    c.getManufacturer())) {
   
     filteredCars.add(c); 
  }
}

Alții, arhitecți, mai scriau:

interface Predicate {
  boolean apply(T t);  
}

class Filter { 

public static Collection 
 do (final Collection items, 
 Predicate pred) {
 
   Collection result = 
      new ArrayList<>();
  
  for(T item : items) {
      if (pred.apply(item)) {
        result.add(c); 
      }
    }
   
   return result;
  }
}

În Java 8 aplicăm:

new Filter().do( allCars, new Predicate() {
  @Override
  
public boolean apply(Car c) {
    return "VW".equals(c.getManufacturer());
  }
);

Și iată cum Java 8 ne oferă "high resolution".

Dar trăim într-o eră a big data și aplicațiile "real-world" se cer a fi scalabile și performante, ca atare nu mai e suficient să procesăm secvențial milioane de înregistrări (dacă cumva a fost vreodată suficient).

Parallel processing și utilizarea optimă a core-urilor de procesor este "everyday business".

Java 8 ne vine în ajutor și acum putem scrie:

Collection filtered = 
  allCars.stream().filter( c -> ("VW".equals(
    c.getManufacturer())) ).collect(
      Collectors.toList()); 

Doar prin acest simplu apel către metoda "parallel()" mă asigur că librăria de stream va face magie și va împărți stream-ul în bucățele mici ce vor fi procesate în paralel.

Job done. Sau nu?

Îmi place să citesc javadoc-uri, consider că e un obicei ce trebuie cultivat pentru că ne poate salva de la multe dezastre. Și am mai cultivat în timp un fel de "simț al pericolului", în special când văd cuvinte mari de genul "parallel", urmate apoi de magia rezultatului.

Ce fire de execuție folosește metoda aceasta? Și folosește destule? Cum determină câte să folosească și ce se întâmplă dacă metoda "parallel()" este apelată paralel la rândul ei?

Documentația spune: "Arranges to asynchronously execute this task in the pool the current task is running in, if applicable, or using the ForkJoinPool.commonPool() if not inForkJoinPool()."

Prin urmare, implicit folosim un singur pool indiferent de câte thread-uri apelează metoda "parallel()" prin întreaga aplicație, deoarece librăria de stream folosește librăria de ForkJoin.

Și mai mult, thread-ul care trimite job-ul de procesare paralelă este el însuși utilizat ca worker. Se mixează astfel thread-urile unui pool cu un thread ce are alt scop. Dacă cumva chiar acel thread prinde o porțiune de procesare ce durează neașteptat de mult, avem riscul unei blocări a procesărilor din cadrul pool-ului tocmai din cauza design-ului conceptului de Fork/Join (thread-ul apelant lucrează ca worker și celelalte thread-uri nu pot adăuga rezultate câtă vreme se așteaptă după finalizarea execuției thread-ului părinte). Avem o problemă!

Avem de a face aici cu fenomenul de Paraquential "[a portmanteau word derived by combining parallel with sequential] The illusion of parallelization. Processing starts out in parallel mode by engaging multiple threads but quickly reverts to sequential computing by restricting further thread commitment." (Edward Hardned, 2014).

Soluția propusă de Oracle este utilizarea explicită a unui ForkJoinPool controlat de către dezvoltator. De tipul:

Collection filtered = 
  allCars. parallel().filter( c -> 
    ("VW".equals(c.getManufacturer())) ).
     collect(Collectors.toList());

Astfel toate task-urile generate de procesarea paralelă rămân în pool-ul specificat.

Un efect pozitiv este acela că putem folosi un timeout pe metoda get(); situație dorită de obicei într-o aplicație real-world.

Dar aceasta ne întoarce la problema de bază: managementul pool-urilor din nou în responsabilitatea dezvoltatorului! Situația devine mai dificilă în combinație cu situațiile complexe generate de mediul multi-thread și cu reglarea atentă a configurărilor în funcție de arhitectura hardware (e.g. procesor). Și iată cum din nou Java ne dă jumătăți de măsură pe când speram la un thread container self-managed (sau măcar easy-managed).

Chiar dacă pentru mulți dintre noi comportamentul implicit al framework-ului de streaming este și va fi mai mult decât suficient, în ceea ce mă privește, metoda parallel va rămâne în lista mea de "dangerous code" în activitățile de programare și code review.

Am atins doar vârful iceberg-ului prin această scurtă analiză.Pentru cei dintre voi curioși să vadă ce alte capcane se ascund chiar în inima librăriei de ForkJoin, vă invit să urmăriți cu atenție un dude cu peste 30 de ani de experiență, ce detaliază foarte bine aceste lucruri în postul său A Java Parallel calamity.

În aceeaşi ediţie ... (24)

▼ TOATE ARTICOLELE ▼

NUMĂRUL 138 - Maps & AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • Connatix
  • BoatyardX
  • .msg systems
  • Yardi
  • Colors in projects

INTERVIURI VIDEO

Ovidiu Simionica a mai scris