Programovanie

Java 101: Súbežnosť Java bez bolesti, 1. časť

S čoraz zložitejšou súčasnosťou aplikácií mnoho vývojárov zistí, že schopnosti Java na nízkej úrovni závitovania sú nedostatočné pre ich programové potreby. V takom prípade by mohol byť čas objaviť Java Concurrency Utilities. Začnite s java.util.concurrent, s podrobným úvodom Jeffa Friesena do rámca Executor, typov synchronizátorov a balíka Java Concurrent Collections.

Java 101: Nová generácia

Prvý článok v tejto novej sérii JavaWorld predstavuje Rozhranie API pre dátum a čas Java.

Platforma Java poskytuje funkcie vlákien na nízkej úrovni, ktoré umožňujú vývojárom písať súbežné aplikácie, kde sa súčasne spúšťajú rôzne vlákna. Štandardné vlákno Java má však aj svoje tienisté stránky:

  • Nízkoúrovňové primitívy súbežnosti Javy (synchronizované, prchavý, počkaj (), upozorniť ()a notifyAll ()) nie sú ľahké na správne použitie. Je tiež ťažké odhaliť a odladiť nebezpečenstvo závitu, ako je uviaznutie, hladové vlákno a rasové podmienky, ktoré sú výsledkom nesprávneho použitia primitívnych prostriedkov.
  • Spoliehajúc sa na synchronizované koordinácia prístupu medzi vláknami vedie k problémom s výkonom, ktoré ovplyvňujú škálovateľnosť aplikácií, čo je požiadavka mnohých moderných aplikácií.
  • Základné možnosti vytvárania vlákien Java sú tiež nízky level. Vývojári často potrebujú konštrukcie na vyššej úrovni, ako sú semafory a fondy vlákien, čo schopnosti vlákien na nízkej úrovni Java neponúkajú. Vďaka tomu budú vývojári vytvárať svoje vlastné konštrukcie, ktoré sú časovo náročné aj náchylné na chyby.

Rámec JSR 166: Concurrency Utilities bol navrhnutý tak, aby vyhovoval potrebám zariadenia na vytváranie závitov na vysokej úrovni. Rámec, ktorý bol zahájený začiatkom roku 2002, bol formalizovaný a implementovaný o dva roky neskôr v prostredí Java 5. Vylepšenia nasledovali v prostredí Java 6, Java 7 a pripravovanom prostredí Java 8.

Táto dvojdielna Java 101: Nová generácia séria predstavuje vývojárov softvéru, ktorí sú oboznámení so základnými vláknami Java, do balíkov a rámca Java Concurrency Utilities. V časti 1 uvádzam prehľad rámca Java Concurrency Utilities a predstavujem jeho rámec Executor, synchronizačné nástroje a balík Java Concurrent Collections.

Pochopenie vlákien Java

Pred ponorením sa do tejto série si osvojte základné znalosti o závitovaní. Začnite s Java 101 úvod do nízkoúrovňových schopností vlákna Java:

  • Časť 1: Predstavujeme vlákna a spustiteľné súbory
  • Časť 2: Synchronizácia vlákna
  • Časť 3: Plánovanie vlákna, čakanie / upozornenie a prerušenie vlákna
  • Časť 4: Skupiny vlákien, volatilita, miestne premenné vlákna, časovače a smrť vlákna

Vo vnútri Java Concurrency Utilities

Rámec Java Concurrency Utilities je knižnica typy ktoré sú určené na použitie ako stavebné bloky na vytváranie súbežných tried alebo aplikácií. Tieto typy sú bezpečné pre vlákna, boli dôkladne testované a ponúkajú vysoký výkon.

Typy v obslužných programoch Java Concurrency Utilities sú usporiadané do malých rámcov; a to Executor framework, synchronizer, concurrent collections, locks, atomic variables, and Fork / Join. Ďalej sú usporiadané do hlavného balíka a pár podbalení:

  • java.util.concurrent obsahuje vysokoúrovňové typy nástrojov, ktoré sa bežne používajú pri súbežnom programovaní. Príklady zahŕňajú semafory, bariéry, skupiny vlákien a súčasné hashmapy.
    • The java.util.concurrent.atomic subbalíček obsahuje nízkoúrovňové triedy nástrojov, ktoré podporujú programovanie bez zámkov bez vlákien na jednotlivých premenných.
    • The java.util.concurrent.locks Subbalíček obsahuje nízkoúrovňové typy nástrojov na zamykanie a čakanie na podmienky, ktoré sa líšia od používania nízkoúrovňovej synchronizácie a monitorov Java.

Rámec Java Concurrency Utilities tiež odhaľuje nízku úroveň porovnávať a vymeniť (CAS) hardvérová inštrukcia, ktorej varianty sú bežne podporované modernými procesormi. CAS je oveľa ľahší ako synchronizačný mechanizmus Java založený na monitoroch a používa sa na implementáciu niektorých vysoko škálovateľných súbežných tried. Založené na CAS java.util.concurrent.locks.ReentrantLock trieda je napríklad výkonnejšia ako ekvivalentná na báze monitora synchronizované primitívne. ReentrantLock ponúka väčšiu kontrolu nad zamykaním. (V časti 2 vysvetlím viac informácií o tom, ako funguje CAS java.util.concurrent.)

System.nanoTime ()

Rámec Java Concurrency Utilities obsahuje long nanoTime (), ktorý je členom java.lang.System trieda. Táto metóda umožňuje prístup k časovému zdroju nanosekundovej zrnitosti na uskutočňovanie relatívnych časových meraní.

V ďalších častiach predstavím tri užitočné funkcie Java Concurrency Utilities, najskôr vysvetlím, prečo sú pre modernú súbežnosť také dôležité, a potom ukážem, ako pracujú na zvýšení rýchlosti, spoľahlivosti, efektívnosti a škálovateľnosti súbežných aplikácií Java.

Rámec exekútora

V závitoch, a úloha je jednotka prace. Jedným z problémov pri nízkoúrovňovom podprocesovaní v prostredí Java je to, že zadávanie úloh je úzko spojené s politikou vykonávania úloh, čo dokazuje zoznam 1.

Zoznam 1. Server.java (verzia 1)

import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; trieda Server {public static void main (String [] args) hodí IOException {ServerSocket socket = nový ServerSocket (9000); while (true) {final Socket s = socket.accept (); Spustiteľný súbor r = nový Spustiteľný súbor () {@Override public void run () {doWork (s); }}; new Thread (r) .start (); }} static void doWork (Socket s) {}}

Vyššie uvedený kód popisuje jednoduchú serverovú aplikáciu (s doWork (zásuvka) ponechané prázdne pre stručnosť). Vlákno servera opakovane volá socket.accept () čakať na prichádzajúcu požiadavku a potom po spustení vlákna obsluhujúceho túto požiadavku.

Pretože táto aplikácia vytvára nové vlákno pre každú požiadavku, nemení sa dobre, keď čelí veľkému počtu požiadaviek. Napríklad každé vytvorené vlákno vyžaduje pamäť a príliš veľa vlákien môže vyčerpať dostupnú pamäť a vynútiť ukončenie aplikácie.

Tento problém by ste mohli vyriešiť zmenou politiky vykonávania úloh. Namiesto neustáleho vytvárania nového vlákna môžete použiť oblasť vlákien, v ktorej by pevný počet vlákien obsluhoval prichádzajúce úlohy. Na vykonanie tejto zmeny by ste však museli aplikáciu prepísať.

java.util.concurrent obsahuje rámec Executor, malý rámec typov, ktoré oddeľujú zadávanie úloh od politík vykonávania úloh. Pomocou rámca Executor je možné ľahko vyladiť politiku vykonávania úloh programu bez nutnosti výrazného prepisovania vášho kódu.

Vo vnútri rámca exekútora

Rámec exekútora je založený na Exekútor rozhranie, ktoré popisuje exekútor ako každý objekt schopný vykonania java.lang.Runnable úlohy. Toto rozhranie deklaruje nasledujúcu osamelú metódu vykonávania a Spustiteľné úloha:

void execute (príkaz Runnable)

Odošlete a Spustiteľné úlohu odovzdaním spustiť (spustiteľné). Ak exekútor nemôže z akéhokoľvek dôvodu vykonať úlohu (napríklad ak bol exekútor vypnutý), táto metóda vyvolá RejectedExecutionException.

Kľúčovým konceptom je to zadanie úlohy je oddelené od politiky vykonávania úlohy, ktorý je opísaný v Exekútor implementácia. The spustiteľný úloha je teda schopná vykonať prostredníctvom nového vlákna, združeného vlákna, volajúceho vlákna atď.

Poznač si to Exekútor je veľmi obmedzený. Napríklad nemôžete vypnúť exekútora alebo určiť, či sa asynchrónna úloha skončila. Spustenú úlohu tiež nemôžete zrušiť. Z týchto a ďalších dôvodov poskytuje rámec Executor rozhranie ExecutorService, ktoré sa rozširuje Exekútor.

Päť z ExecutorServiceZvlášť pozoruhodné sú metódy:

  • boolean awaitTermination (dlhý časový limit, jednotka TimeUnit) blokuje volajúce vlákno, kým sa všetky úlohy nedokončia po požiadavke na vypnutie, nevyprší časový limit alebo sa nepreruší súčasné vlákno, podľa toho, čo nastane skôr. Maximálny čas čakania je uvedený v čas vypršala táto hodnota je vyjadrená v jednotka jednotky uvedené v TimeUnit enum; napríklad, TimeUnit.SECONDS. Táto metóda hodí java.lang.InterruptedException keď je súčasné vlákno prerušené. Vracia sa to pravda keď je exekútor ukončený a nepravdivé keď uplynie časový limit pred ukončením.
  • boolean isShutdown () vracia pravda keď bol exekútor odstavený.
  • vypnutie void () iniciuje systematické vypínanie, v ktorom sa vykonávajú skôr zadané úlohy, ale neprijímajú sa žiadne nové úlohy.
  • Budúce odoslanie (úloha s výzvou) zadá úlohu vrátenia hodnoty na vykonanie a vráti a Budúcnosť predstavujúce čakajúce výsledky úlohy.
  • Budúce odoslanie (spustiteľná úloha) predkladá a Spustiteľné úloha na vykonanie a vrátenie a Budúcnosť ktorá predstavuje túto úlohu.

The Budúcnosť rozhranie predstavuje výsledok asynchrónneho výpočtu. Výsledok je známy ako a budúcnosť pretože zvyčajne nebude k dispozícii skôr ako v budúcnosti. Môžete vyvolať metódy na zrušenie úlohy, vrátenie výsledku úlohy (nekonečné čakanie alebo uplynutie časového limitu po dokončení úlohy) a určenie, či bola úloha zrušená alebo skončila.

The Vyvolávateľná rozhranie je podobné Spustiteľné rozhranie v tom, že poskytuje jedinú metódu popisujúcu vykonanú úlohu. Na rozdiel od Spustiteľnéje void run () metóda, Vyvolávateľnáje V call () vyvolá výnimku metóda môže vrátiť hodnotu a vyvolať výnimku.

Vykonávacie továrenské metódy

V určitom okamihu budete chcieť získať exekútora. Rámec exekútora dodáva Exekútori úžitková trieda na tento účel. Exekútori ponúka niekoľko továrenských metód na získavanie rôznych druhov exekútorov, ktoré ponúkajú konkrétne politiky vykonávania vlákien. Tu sú tri príklady:

  • ExecutorService newCachedThreadPool () vytvorí fond vlákien, ktorý podľa potreby vytvorí nové vlákna, ale ktorý znova použije predtým vytvorené vlákna, keď sú k dispozícii. Vlákna, ktoré neboli použité 60 sekúnd, sú ukončené a odstránené z medzipamäte. Táto oblasť vlákien zvyčajne zlepšuje výkonnosť programov, ktoré vykonávajú veľa krátkodobých asynchrónnych úloh.
  • ExecutorService newSingleThreadExecutor () vytvorí vykonávateľa, ktorý používa jedno pracovné vlákno operujúce mimo neobmedzeného frontu - úlohy sa pridajú do frontu a vykonávajú sa postupne (súčasne nie je aktívnych viac ako jedna úloha). Ak sa toto vlákno ukončí zlyhaním počas vykonávania pred vypnutím exekútora, vytvorí sa nové vlákno, ktoré zaujme svoje miesto, keď je potrebné vykonať ďalšie úlohy.
  • ExecutorService newFixedThreadPool (int nThreads) vytvorí oblasť vlákien, ktorá opakovane používa pevný počet vlákien pracujúcich mimo zdieľaného neobmedzeného frontu. Najviac nVlákna vlákna aktívne spracúvajú úlohy. Ak sa zadajú ďalšie úlohy, keď sú všetky vlákna aktívne, počkajú vo fronte, kým nebude vlákno k dispozícii. Ak sa ktorékoľvek vlákno ukončí zlyhaním počas vykonávania pred vypnutím, vytvorí sa nové vlákno, ktoré zaujme svoje miesto, keď je potrebné vykonať ďalšie úlohy. Vlákna spoločného fondu existujú, kým sa nezastaví exekútor.

Rámec Exekútor ponúka ďalšie typy (napríklad ScheduledExecutorService rozhranie), ale typy, s ktorými budete pravdepodobne pracovať najčastejšie, sú ExecutorService, Budúcnosť, Vyvolávateľnáa Exekútori.

Viď java.util.concurrent Javadoc na preskúmanie ďalších typov.

Práca s rámcom exekútora

Zistíte, že s rámcom Exekútora sa dá pomerne ľahko pracovať. V zozname 2 som použil Exekútor a Exekútori nahradiť príklad servera zo zoznamu 1 škálovateľnejšou alternatívou založenou na združovaní vlákien.

Zoznam 2. Server.java (verzia 2)

import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.Executor; import java.util.concurrent.Executors; trieda Server {statický fond exekútorov = Executors.newFixedThreadPool (5); public static void main (String [] args) hodí IOException {ServerSocket socket = nový ServerSocket (9000); while (true) {final Socket s = socket.accept (); Spustiteľný súbor r = nový Spustiteľný súbor () {@Override public void run () {doWork (s); }}; pool.execute (r); }} static void doWork (Socket s) {}}

Zoznam 2 použití newFixedThreadPool (int) získať exekútora založeného na združovaní vlákien, ktorý opätovne použije päť vlákien. Tiež nahrádza new Thread (r) .start (); s pool.execute (r); na vykonávanie spustiteľných úloh cez ktorékoľvek z týchto vlákien.

Výpis 3 predstavuje ďalší príklad, v ktorom aplikácia číta obsah ľubovoľnej webovej stránky. Ak nie je obsah k dispozícii do piatich sekúnd, odošle výsledné riadky alebo chybové hlásenie.

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