Differences

This shows you the differences between two versions of the page.

Link to this comparison view

apd:laboratoare:04 [2020/11/04 13:42]
alexandru.hogea [Synchronized]
apd:laboratoare:04 [2023/10/08 16:32] (current)
dorinel.filip move
Line 1: Line 1:
-===== Laboratorul 04 - Introducere în Java Multithreading ===== +===== Laboratorul 4 - Introducere în Java Multithreading =====
- +
-Responsabili:​ Alexandru-Ionuț Mustață, Gabriel Guțu-Robu +
- +
-==== Suportul de multithreading oferit de Java ==== +
- +
-Spre deosebire de limbajul C unde implementarea unui thread și a mecanismelor de sincronizare depinde de o bibliotecă ce este specifică unui anumit tip de sistem de operare (Linux, Windows, MacOSX), Java oferă suport pentru lucrul cu thread-urile direct din cadrul SDK său. +
- +
- +
-=== Implementarea unui nou thread === +
- +
-În Java există două modalități prin care putem să implementăm un nou thread. +
- +
-Prima variantă de implementare are la bază crearea unei clase ce implementează interfața **Runnable** ce conține metoda **void run()**. Codul ce va reprezenta logica noului thread va fi plasat în interiorul acestei metode. +
-<code Java> +
-public class Task implements Runnable { +
-    public void run() { +
-        System.out.println("​Hello from my new thread!"​);​ +
-    } +
-+
-</​code>​ +
- +
- +
-O altă metodă de implementare a unui thread constă din crearea unei clase ce extinde clasa **Thread** și suprascrie (overrides) metoda **void run()** din cadrul acesteia. La fel ca în primul caz, logica noului thread va fi implementată în cadrul acestei metode. +
-<code Java> +
-public class MyThread extends Thread { +
-    public void run() { +
-        System.out.println("​Hello from my new thread!"​);​ +
-    } +
-+
-</​code>​ +
- +
- +
-=== Rularea în paralel a noului thread === +
- +
-În cazul în care s-a folosit mecanismul de implementare a interfeței **Runnable**,​ atunci pentru a putea crea un thread nou ce va conține logica definită în clasa **Task** se va crea o instanță a clasei **Task** ce va fi furnizată ca parametru constructorului clasei **Thread**. Pentru a rula în paralel noul thread creat cu ajutorul constructorului se va apela metoda **public void start()** a acestuia. +
- +
-<code Java> +
-public class Main { +
-    public static void main(String[] args) { +
-        Thread t = new Thread(new Task()); +
-        t.start();​ +
-    } +
-+
-</​code>​ +
- +
-În cazul în care s-a extins clasa **Thread** pentru implementarea noului tip de thread se va putea crea un thread nou prin instanțierea directă a clasei **MyThread** și pentru a se porni excecuția în paralel a acestuia se va apela metoda **public void start()** moștenită din cadrul clasei **Thread**. +
- +
-<code Java> +
-public class Main { +
-    public static void main(String[] args) { +
-        MyThread t = new MyThread();​ +
-        t.start();​ +
-    } +
-+
-</​code>​ +
- +
-<note important><​color red>​**Atenție!** Există o distincție foarte importantă între metodele de **start()** și **run()** ale clasei **Thread**! Atunci când este apelată metoda **run()** codul prezent în aceasta se va executa secvențial în cadrul thread-ului care a apelat-o. Atunci când este apelată metoda **start()** JVM (Java Virtual Machine) va crea un nou thread ce va executa instrucțiunile prezente în cadrul metodei **run()** în paralel cu thread-ul care a apelat metoda **start()**.</​color></​note>​ +
- +
-=== Așteptarea terminării execuției unui thread === +
-Pentru a aștepta terminarea execuției unui thread, Java ne pune la dispoziție metoda **public final void join()** a clasei **Thread**. Trebuie ținut cont de faptul că această metodă poate arunca excepții de tipul **InterruptedException**. +
-<code Java> +
-public class Main { +
-    public static void main(String[] args) { +
-        MyThread t = new MyThread();​ +
-        t.start();​ +
-         +
-        try { +
-            t.join(); +
-        } catch (InterruptedException e) { +
-            e.printStackTrace();​ +
-        } +
-    } +
-+
-</​code>​ +
- +
-=== Trimiterea de parametrii unui thread și obținerea de rezultate de la acesta === +
-Pentru a trimite parametrii unui thread se va folosi constructorul clasei care inglobeaza logica thread-ului pe care dorim sa il implementam (indiferent de metoda de implementare a acestuia - prin moștenire sau implementeare de interfață). Pentru a obține un rezultat de la un thread care și-a terminat execuția <color red>​(apelul metodei **join()** a întors)</​color>​ putem folosi fie metode de tip getter care vor întoarce rezultatul fie putem accesa în mod direct câmpul rezultat în cazul ân care acest este definit ca public. +
- +
-<code Java> +
-public class Task implements Runnable { +
-     +
-    private int id; +
-    private int result; +
-     +
-    public Task(int id) { +
-        this.id = id; +
-    } +
-     +
-    public void run() { +
-        result = id * id; +
-    } +
-     +
-    public int getResult() { +
-        return result; +
-    } +
-     +
-+
-</​code>​ +
- +
-<code Java> +
-public class Main { +
-     +
-    public static void main(String[] args) { +
-        int NUMBER_OF_THREADS = 4; +
-        Thread[] t = new Thread[NUMBER_OF_THREADS];​ +
-         +
-        for (int i = 0; i < NUMBER_OF_THREADS;​ ++i) { +
-            t[i] = new Thread(new Task(id));​ +
-            t[i].start();​ +
-        } +
-         +
-        for (int i = 0; i < NUMBER_OF_THREADS;​ ++i) { +
-            try { +
-                t[i].join();​ +
-                System.out.println("​Thread " + i + " computed result " + t[i].getResult() + "​."​);​ +
-            } catch (InterruptedException e) { +
-                e.printStackTrace();​ +
-            } +
-        } +
-    } +
-+
-</​code>​ +
- +
-==== Synchronized ==== +
- +
-Cuvântul rezervat **synchronized** are rolul de a defini blocuri de cod și metode ce reprezintă secțiuni/​regiuni critice. +
- +
-<code Java> +
-public class MyConcurrentArray<​T>​ { +
- +
-    private static int numberOfInstances = 0; +
-    private T[] content; +
-     +
-    public MyConcurrentArray(int size) { +
-        if (size > 0) { +
-            content = new T[size]; +
-        } else { +
-          throw new RuntimeException("​Negative size provided for MyConcurrentArray instantiation."​);​ +
-        } +
-         +
-        synchronized(MyConcurrentArray.class) { +
-            ++numberOfInstances;​ +
-        } +
-    } +
-     +
-    //Metodă sincronizată. +
-    public synchronized T get(int index) { +
-        if (index < content.length) { +
-            return content[index];​ +
-        } +
-        throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);​ +
-    } +
-     +
-    public void set(int index, T newT) { +
-        //Bloc de cod sincronizat ce folosește instanța curentă (this) pe post de zăvor. +
-        synchronized(this) { +
-            if (index < content.length) { +
-                content[index] = newT; +
-            } +
-            throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);​ +
-        } +
-    } +
-     +
-    //Metodă sincronizată statică. +
-    public static synchronized int getNumberOfInstances(){ +
-        return numberOfInstances;​ +
-    } +
-     +
-    public void size() { +
-        return content.length;​ +
-    } +
-+
-</​code>​ +
- +
-Pentru a putea discuta comportamentul blocurilor de cod sincronizate vom folosi exemplul de mai sus în care este definită o clasă ce implementează sumar conceptul de vector de dimensiune fixă ce poate fi folosit într-un program multithreading (structură de date thread-safe). +
- +
-Se observă faptul ca metoda **get** este definită ca fiind **synchronized**. În momentul când un thread va apela aceasta metodă pe o instanță a clasei **MyConcurrentArray** va trebui mai întâi să obțină [[https://​en.wikipedia.org/​wiki/​Monitor_%28synchronization%29|monitorul]] asociat acesteia (obiectului) pentru a putea executa corpul metodei. Dacă monitorul nu este deținut de nici un alt thread, atunci thread-ul apelant va putea să execute corpul de instrucțiuni al metodei, altfel se va bloca (va aștepta) până când acesta devine disponibil. La încheierea execuției corpului metodei acesta restituie accesul la monitor. +
-  +
-În cazul metodei **set** este prezent un bloc de instrucțiuni sincronizat. Acesta va folosi monitorul obiectului desemnat între paranteze pentru a oferi thread-ului curent acces excluziv în cadrul regiunii critice. În cazul nostru se folosește instanța curentă a obiectului (**this**). Mecanismul de intrare și ieșire în/din secțiunea critică este același ca cel prezentat mai sus pentru cazul metodelor sincronizate. +
- +
-În cazul apelării metodelor sincronizate statice se va încerca obținerea monitorului asociat clasei pentru a executa codul acestora. Acest lucru se întâmplă deoarce o metodă statică aparține clasei, ea nu aparține nici unei instanțe a clasei. De aceea când este necesar accesul exclusiv la un câmp static dintr-o clasă se va folosi clasa în antetul blocului synchronized (MyClass.class,​ după cum este prezentat și în constructorul din exemplu). +
- +
-<note important>​**Atenție!** Metodele și blocurile de cod **synchronized** din Java sunt **reentrante**. Dacă un thread a obținut monitorul unui obiect, atunci el va putea intra în orice alt bloc și metodă sincronizate ce sunt asociate cu acel obiect (implicit cu acel monitor). Acest comportament nu este activat în mod implicit pentru **pthread_mutex_t** definit în C (se poate obține prin specificarea de atribute la creare, **Hint**: PTHREAD_MUTEX_RECURSIVE).</​note>​ +
- +
- +
-==== CyclicBarrier ==== +
- +
-Bariera ciclică reprezintă un mecanism de (re)sincronizare al mai multor thread-uri care are scopul de a bloca un număr specificat de thread-uri și de a le lăsa sa își continua execuția într-un mod sincron după ce toate au apelat metoda acesteia de resincronizare. În Java aceast mecanism este reprezentat de clasa CyclicBarrier. La instanțierea acesteia se va specifica numărul de thread-uri pe care aceasta le va resincroniza. Acest mecanism de sincronizare al thread-urilor este util în cazul algoritmilor iterativi ce sunt rulați în paralel și au nevoie de o etapă de resincronizare a firelor de execuția înainte de a trece la noua iterație de calcul. Trebuie ținut cont de faptul că apelul metodei **await()** pe bariera ciclică poate arunca o excepție de tipul **BrokenBarrierException** sau **InterruptedException**. +
- +
-<code Java> +
-public class Task extends Thread { +
-  +
-    private int id; +
-  +
-    public Task(int id) { +
-        this.id = id; +
-    } +
-  +
-    public void run() { +
-        iterationCounter = 0; +
-        while (!solved) { +
-            executeAlgorithmStep();​ +
-             +
-            try { +
-                //​Resincronizarea thread-urilor pentru urmatorul pas al algoritmului. +
-                Main.barrier.await();​ +
-                ++iterationCounter;​ +
-            } catch (BrokenBarrierException | InterruptedException e) { +
-                e.printStackTrace();​ +
-            } +
-        } +
-    } +
-  +
-+
-</​code>​ +
- +
-<code Java> +
-public class Main { +
-  +
-    public static CyclicBarrier barrier; +
- +
-    public static void main(String[] args) { +
-        int NUMBER_OF_THREADS = 4; +
-        barrier = new CyclicBarrier(NUMBER_OF_THREADS);​ +
-        Task[] t = new Task[NUMBER_OF_THREADS];​ +
-  +
-        for (int i = 0; i < NUMBER_OF_THREADS;​ ++i) { +
-            t[i] = new Task(id); +
-            t[i].start();​ +
-        } +
-  +
-        for (int i = 0; i < NUMBER_OF_THREADS;​ ++i) { +
-            try { +
-                t[i].join();​ +
-            } catch (InterruptedException e) { +
-                e.printStackTrace();​ +
-            } +
-        } +
-    } +
-+
-</​code>​ +
- +
-{{:​apd:​laboratoare:​cyclicbarrier.pdf|CheatSheet CyclicBarrier}} +
- +
-==== Volatile ==== +
- +
-Cuvântul rezervat **volatile** asociat unei variabile specifică faptul că acea variabilă nu va intra în procesul de cacheing (nici la nivel de regiștrii, nici la nivel de cache L1, L2, L3) și fiecare scriere și citire asociată acesteia se va realiza direct lucrând cu memoria RAM. Acest lucru este util în prevenția citirii unei date vechi din cache-ul sau registrul asociat core-ului pe care rulează un thread în momentul în care variabila a căpătat o valoare nouă pe alt thread ce lucrează pe alt core. +
- +
-<code Java> +
-public static volatile int counter = 0; +
-</​code>​ +
- +
-<note important>​Este important de menționat faptul că variabilele volatile cresc timpul de execuție al programului,​ deoarece fiecare modificare adusă lor trebuie propagată și celorlalte thread-uri, în timp ce variabilele normale pot fi cache-uite la un moment dat chiar până la nivelul regiștrilor procesorului (în cazul unui contor de buclă). +
-</​note>​ +
- +
-==== Variabile atomice ==== +
- +
-În cadrul laboratorului 2 am observat cum operația de incrementare (exemplul cu instrucțiunea a+=2) nu este o operație atomică (operație ce nu poate fi divizată atunci când este executată de către un thread). Java oferă suport pentru o serie de tipuri de date (tipuri atomice) ce au asociate operații atomice [[https://​docs.oracle.com/​javase/​8/​docs/​api/?​java/​util/​concurrent/​atomic/​package-summary.html|[1]]],​ [[https://​docs.oracle.com/​javase/​tutorial/​essential/​concurrency/​atomicvars.html|[2]]]. Acestea sunt utile pentru a fi folosite pe post de contori sau acumulatori fără a mai folosi mecanisme de sincronizate. +
- +
-==== Exerciții ==== +
- +
-1. Creați un program care să lanseze un număr de thread-uri egal cu numărul de core-uri de care dispune calculatorul vostru. Fiecare thread trebuie să afișeze la consolă un text de tipul ”Hello from thread #id”. **[2P]** +
- +
-2. Rezolvați bug-urile prezente în {{:​apd:​laboratoare:​lab_04_skel.zip|arhiva de laborator}}. Folosiți-vă de **hint-uri** din surse. **[3P]** +
- +
-3. Paralelizați dublarea elementelor unui vector. **[2P]** +
- +
-4. Paralelizați algoritmul Floyd-Warshall. **[3P]** +
- +
- +
  
 +Documentația de laborator s-a mutat la [[https://​mobylab.docs.crescdi.pub.ro/​docs/​parallelAndDistributed/​introduction|această adresă]].
apd/laboratoare/04.1604490173.txt.gz · Last modified: 2020/11/04 13:42 by alexandru.hogea
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0