Limbajul de programare Java și Java virtual machine (JVM) au fost proiectate pentru a suporta programarea concurentă și toate execuțiile au loc în contextul firelor de execuție. Obiectele și resursele pot fi accesate de multiple fire de execuție separate.

Concurența este abilitatea de a rula mai multe părți ale unui program sau mai multe programe în paralel. Dacă sarcini consumatoare de timp pot fi realizate asincron sau în paralel, acest îmbunătăți debitul și interactivitatea programului.

Un calculator modern are mai multe procesoare sau mai multe nuclee în termen de un procesor. Capacitatea de a atrage aceste multi-core poate fi cheia pentru o aplicație de volum de mare succes.

Procese vs. Fire de execuție (threads)

modificare

Distincția dintre Procese și Fire de execuție este importantă.

  • Procesul: rulează independent și izolat de alte procese. El nu poate accesa direct datele partajate în alte procese. Resursele procesului îi sunt alocate prin sistemul de operare, de exemplu, memorie și timp de procesor.
  • Firele de execuție: așa-numitele procese ușoare, care au propria lor stiva de apel, dar pot accesa datele partajate. Fiecare fir are propria memorie cache. În cazul în care un fir citește datele partajate se stochează aceste date în propriul cache de memorie. Un fir poate reciti datele partajate. Aceasta este explicat în Java, modelul fiind parte din acest tutorial.

Într-o aplicație Java lucrați cu mai multe fire de execuție pentru a realiza procesarea paralelă sau un comportament asincron.

Legea lui Amdahl

modificare

Concurența promite să efectueze anumite sarcini mai repede pentru ca aceste sarcini pot fi împărțite în sub-sarcini și aceste sub-sarcini pot fi executate în paralel. Desigur execuția este limitată de părți ale sarcinii, care pot fi efectuate în paralel.

Posibila creștere teoretică a performanței poate fi calculată prin Legea lui Amdahl. Dacă F este procentul de program ce nu poate rula în paralel și N este numărul de procese atunci câștigul maxim de performanță este de 1 / (F + ((1-F) / n)).

Probleme de concurența

modificare

Firele de execuție pot avea acolo o stiva de apel propriu, dar poate accesa, de asemenea, date partajate. Prin urmare, aveți două probleme de bază, vizibilitatea și probleme de acces.

O problemă de vizibilitate apare dacă firul A citește date comune, care sunt ulterior modificate prin firul B și firul A este conștient de această schimbare.

O problemă de acces poate apărea dacă mai multe fire de acces și schimba aceleași date partajate în același timp.

Vizibilitate și problema de acces poate duce la

  • Esec existential: Programul nu mai reacționeze din cauza problemelor în accesul concurent de date, de exemplu, blocaje.
  • Esec de siguranta: Programul creează date incorecte

Concurența in Java

modificare

Processes and Threads

modificare

Un program Java rulează în propriul proces și implicit într-un singur fir de execuție. Java sprijină fire ca parte limbajul Java prin codul thread. Aplicația Java poate crea subiecte noi prin această clasă.

Java 1.5 oferă, de asemenea, suport îmbunătățit pentru concurență în pachetul java.util.concurrent.

Încuietori și sincronizare de threaduri

modificare

Java oferă încuietori pentru a proteja anumite părți de codificare care urmează să fie executate de mai multe fire de execuție, în același timp. Cel mai simplu mod de blocare pentru o anumită metodă sau a unor clase Java este de a defini metoda sau clasa cu cuvinte cheie sincronizate (synchronized).

Cuvantul cheie synchronized asigura

  • că numai un singur fir poate executa un bloc de cod, în același timp;
  • că fiecare thread de înregistrare a unei bloc sincronizat de cod vede efectele tuturor modificărilor anterioare, care au fost pazite de aceeași blocare.

Sincronizarea este necesara pentru accesul reciproc exclusiv la blocuri de comunicare și de încredere între fire.

Puteți utiliza cuvintul cheie synchronized pentru definirea unei metode. Acest lucru ar asigura că un singur fir poate introduce această metodă, în același timp. Alte subiecte care se astepta la această metodă s-ar aștepta până la primele fire de frunze de această metodă.

public synchronized void critial() {
  // some thread critical stuff
  // here
}

Puteți folosi, de asemenea, cuvântul cheie synchronized pentru a proteja blocuri de cod într-o metodă. Acest bloc este pazita de o cheie, care poate fi fie un șir sau un obiect. Această cheie este numit de blocare. Toate cod care este protejat de aceeași blocare pot fi executate numai de un singur fir, în același timp.

De exemplu următoarea structure de data va asigura că un singur fir poate accesa bloc interioară a add() și next() ca si metode.

package de.vogella.pagerank.crawler;

import java.util.ArrayList;
import java.util.List;

/**
 * Data structure for a web crawler. Keeps track of the visited sites and keeps
 * a list of sites which needs still to be crawled.
 * 
 * @author Lars Vogel
 * 
 */

public class CrawledSites {
  private List<String> crawledSites = new ArrayList<String>();
  private List<String> linkedSites = new ArrayList<String>();

  public void add(String site) {
    synchronized (this) {
      if (!crawledSites.contains(site)) {
        linkedSites.add(site);
      }
    }
  }

  
/**
   * Get next site to crawl. Can return null (if nothing to crawl)
   */

  public String next() {
    if (linkedSites.size() == 0) {
      return null;
    }
    synchronized (this) {
      // Need to check again if size has changed
      if (linkedSites.size() > 0) {
        String s = linkedSites.get(0);
        linkedSites.remove(0);
        crawledSites.add(s);
        return s;
      }
      return null;
    }
  }

}

Volatile

modificare

În cazul în care o variabilă este declarată cu cuvântul cheie volatile, atunci este garantat că orice fir care citeste un câmp va vedea valoarea cea mai recent scris. Cuvântul cheie volatile nu va efectua nici o blocare exclusivă comun de variabila.

Ca in cazul Java 5 accesul de scriere la o variabilă volatile va actualiza, de asemenea, variabile non-volatile care au fost modificate de către același fir. Acest lucru poate fi, de asemenea, utilizate pentru a actualiza valorile unei variabile de referință, de exemplu, pentru o persoană variabilă volatile. În acest caz, trebuie să utilizați o persoana variabilă temporară și de a folosi setter pentru a inițializa variabilă și apoi atribuiți variabila temporară a variabila finală. Acest lucru va face atunci adresa schimbări ale acestei variabile și valorile vizibile pentru alte fire.

Modelul de memorie Java

modificare

Vedere de ansamblu

modificare

Modelul de memorie Java descrie comunicarea dintre memoria de fire și memoria principală a aplicației. Acesta definește regulile de modul în care schimbările în memoria face prin fire sunt propagate la alte fire. Modelul de memorie Java definește, de asemenea, situațiile în care un fir de re-fresh proprie de memorie din memoria principală. De asemenea, descrie prin operațiunile sunt atomice și comanda operațiunilor.

Operații atomice

modificare

O operație atomică este o operație care se realizează ca o singură unitate de lucru, fără posibilitatea de interferența de la alte operațiuni. Specificatia Java garantează că citirea sau scrierea unei variabile este o operație atomică (excepția cazului în care variabila este de tip long sau double). Variabile operațiuni de tip long sau double sunt doar atomice în cazul în care a declarat cu cuvântul cheie volatile.

Presupunem i este definit ca int.Comanda i + + (creștere), operațiunea nu o operație atomică în Java. Acest lucru este valabil și pentru celelalte tipuri numerice, de exemplu, long. etc).

Operatia i + + funcționaza citește prima valoare, care sunt stocate în I (operații atomice) și apoi se adaugă inca unu (operație atomică). Dar între citire și scriere valoarea iar s-a schimbat.

Deoarece Java 1.5 oferă variabile atomice, de exemplu, AtomicInteger sau AtomicLong care furnizează metode cum ar fi getAndDecrement (), getAndIncrement () și getAndSet (), care sunt atomice.

Actualizări de memorie în cod sincronizate

modificare

Modelul de memorie Java garantează că fiecare fir de intrarea într-un bloc sincronizat de cod vede efectele tuturor modificărilor anterioare care au fost pazite de aceeasi blocare.

Imuabilitatea și copii defensive

modificare

Imuabilitatea

modificare

Cel mai simplu mod de a evita problemele cu concurența este de a partaja numai date imuabile între fire. Datele imuabile sunt datele care nu pot fi schimbate.

Pentru a face o clasă imuabila se face

  • toate campurile finale
  • clasa sa fie declarata cu atributul final
  • Această referință nu este permis să scape în timpul construcției
  • Orice domenii care se referă la obiecte de date mutabile sunt

1. private; 2. nu au metoda setter; 3. ele nu sunt returnate direct de altfel, expuse la un appellant; 4. în cazul în care sunt modificate intern în clasa această schimbare nu este vizibilă și nu are nici un efect în afara clasei.

O clasă imuabila poate avea unele date mutabile care se utilizează pentru a conduce de stat, ci din afara acestei clase, nici vreun atribut din această clasă se pot schimba.

Pentru toate domenii mutabile, de exemplu, Tablouri, care sunt trecute din afara clasei în timpul fazei de construcție, clasa are nevoie pentru a face o copie defensivă, a elementelor pentru a se asigura că nici un alt obiect din afara încă mai pot modifica datele.

Copii defensive

modificare

Trebuie protejat clase de asteptare cod. Să presupunem că Prefixul telefonic va face tot posibilul pentru a schimba datele într-un mod pe care nu l-am aștepta. În timp ce acest lucru este valabil mai ales în cazul de date imuabile, este, de asemenea, valabil și pentru date non-imuabile care încă nu vă așteptați ca aceste date sunt schimbate în afara clasei.

Pentru a proteja clasa de care ar trebui să vă copiați datele pe care le primiți și să se întoarcă doar copii ale datelor pentru codul de apel.

Următorul exemplu creează o copie a unei liste (ArrayList) și returnează doar o copie a listei. În acest fel clientul de această clasă nu poate elimina elemente din listă.

package de.vogella.performance.defensivecopy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyDataStructure {
  List<String> list = new ArrayList<String>();

  public void add(String s) {
    list.add(s);
  }

  
/**
   * Makes a defensive copy of the List and return it
   * This way cannot modify the list itself 
   * 
   * @return List<String>
   */

  public List<String> getList() {
    return Collections.unmodifiableList(list);
  }
}

Threaduri in Java

modificare

Baza de mijloace de concurență sunt in clasa java.lang.Threads. Un fir de execuție este un obiect de tip java.lang.Runnable. Runnable este o interfață ce defineste metoda run(). Această metodă se numește de obiect filet și conține lucrarea care trebuie făcută. Prin urmare, "Runnable" este sarcina de a efectua. Firul de execuție este muncitorul care face această sarcină . Următorul exemplu demonstrează o sarcină (Runnable), care numără suma de un anumit interval de numere.

package de.vogella.concurrency.threads;

/**
 * MyRunnable will count the sum of the number from 1 to the parameter
 * countUntil and then write the result to the console.
 * <p>
 * MyRunnable is the task which will be performed
 * 
 * @author Lars Vogel
 * 
 */

public class MyRunnable implements Runnable {
  private final long countUntil;

  MyRunnable(long countUntil) {
    this.countUntil = countUntil;
  }

  @Override
  public void run() {
    long sum = 0;
    for (long i = 1; i < countUntil; i++) {
      sum += i;
    }
    System.out.println(sum);
  }
}

Următorul exemplu demonstrează utilizarea Thread și clasa Runnable.

	package de.vogella.concurrency.threads;

import java.util.ArrayList;
import java.util.List;

public class Main {

  public static void main(String[] args) {
    // We will store the threads so that we can check if they are done
    List<Thread> threads = new ArrayList<Thread>();
    // We will create 500 threads
    for (int i = 0; i < 500; i++) {
      Runnable task = new MyRunnable(10000000L + i);
      Thread worker = new Thread(task);
      // We can set the name of the thread
      worker.setName(String.valueOf(i));
      // Start the thread, never call method run() direct
      worker.start();
      // Remember the thread for later usage
      threads.add(worker);
    }
    int running = 0;
    do {
      running = 0;
      for (Thread thread : threads) {
        if (thread.isAlive()) {
          running++;
        }
      }
      System.out.println("We have " + running + " running threads. ");
    } while (running > 0);

  }
}

Utlizand clasa Thread in mod direct are urmatoarele dezavantaje

  • Crearea unui nou fir de execuție cauzeaza o problema de supraperformanță;
  • Prea multe fire pot duce la performanțe reduse, pentru ca procesorul are nevoie a comuta între aceste fire;
  • Nu se poate controla cu ușurință numărul de fire, de aceea aveți dreptul să executați în afară de erori de memorie datorita acestor fire multiple.

Pachetul java.util.concurrent oferă suport îmbunătățit pentru concurență față de utilizarea directă de fire. Acest pachet este descris în secțiunea următoare.

Piscine de threaduri cu cadru de lucru executor

modificare

Puteți găsi aceste exemple în secțiunea sursă a proiectului Java numită de.vogella.concurrency.threadpools.

Piscine cu thread-uri pot gestiona un fond de thread lucrător. Piscinele cu fir de execuție conține o coadă de lucru care deține atribuții de așteptare pentru a fi executat.

O piscina de threaduri poate fi descrisa ca o colectie de obiecte de tip Runnable (coada de lucru) si o conexiune de fire de execuție. Aceste threaduri ruleaza constant si verifica coada de lucru pentru o noua munca. Daca exista munca ce trebuie executata atunci executa acest Runnable. Clasa Thread in sine ne aduce o metoda, adica executa (Runnable r) pentru a adauga un nou obiect de tip Runnabale in coada de lucru.

Cadrul de lucru executor ne da exemple de implementare a interfetei java.util.concurent.Executor, Executors.newFixedThreadPool (int n) ce va crea n worker threads. Comanda ExecutorService adauga ciclu de viata metodelor catre Executor ceea ce permite sa inchizi Executorul si sa astepti terminarea.

Dacă vreți să folosiți o piscină de fire cu un fir ce execută mai multe rulabile, puteți folosi metoda Executors.newSingleThreadExecutor()

Creati din nou Runnable

package de.vogella.concurrency.threadpools;

/**
 * MyRunnable will count the sum of the number from 1 to the parameter
 * countUntil and then write the result to the console.
 * <p>
 * MyRunnable is the task which will be performed
 * 
 * @author Lars Vogel
 * 
 */

public class MyRunnable implements Runnable {
  private final long countUntil;

  MyRunnable(long countUntil) {
    this.countUntil = countUntil;
  }

  @Override
  public void run() {
    long sum = 0;
    for (long i = 1; i < countUntil; i++) {
      sum += i;
    }
    System.out.println(sum);
  }
}

Acum rulati rulabilele (runnables) cu cadrul de lucru executor

package de.vogella.concurrency.threadpools;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
  private static final int NTHREDS = 10;

  public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
    for (int i = 0; i < 500; i++) {
      Runnable worker = new MyRunnable(10000000L + i);
      executor.execute(worker);
    }
    // This will make the executor accept no new threads
    // and finish all existing threads in the queue
    executor.shutdown();
    // Wait until all threads are finish
    executor.awaitTermination();
    System.out.println("Finished all threads");
  }
}

In acest fel thred-urile pot returna niste valori (thread-uri purtatoare de mesaj), atunci folosesc clasa java.util.concurrent.Callable.

Bibliografie

modificare
  • Goetz, Brian; Joshua Bloch; Joseph Bowbeer; Doug Lea; David Holmes; Tim Peierls (). Java Concurrency in Practice. Addison Wesley. ISBN 0-321-34960-1. 
  • Lea, Doug (). Concurrent Programming in Java: Design Principles and Patterns. Addison Wesley. ISBN 0-201-31009-0. 

Legături externe

modificare