În această lecție, vom vorbi în general despre lucrul cu clasa java.lang.ThreadLocal<> și despre cum să o folosim într-un mediu cu mai multe fire.

Clasa ThreadLocal este folosită pentru a stoca variabile. O caracteristică distinctivă a acestei clase este că păstrează o copie independentă separată a unei valori pentru fiecare fir care o folosește.

Aprofundând în funcționarea clasei, ne putem imagina o hartă care mapează firele de execuție la valori, din care firul de execuție curent ia valoarea corespunzătoare atunci când trebuie să o folosească.

Constructor de clasă ThreadLocal

Constructor Acțiune
ThreadLocal() Creează o variabilă goală în Java

Metode

Metodă Acțiune
obține() Returnează valoarea variabilei locale a firului curent
a stabilit() Setează valoarea variabilei locale pentru firul curent
elimina() Îndepărtează valoarea variabilei locale a firului curent
ThreadLocal.withInitial() Metodă suplimentară din fabrică care stabilește valoarea inițială

fii gata()

Să scriem un exemplu în care creăm două contoare. Prima, o variabilă obișnuită, va fi pentru numărarea numărului de fire. Al doilea îl vom încheia într-un ThreadLocal . Și vom vedea cum lucrează împreună. Mai întâi, să scriem o clasă ThreadDemo care moștenește Runnable și conține datele noastre și metoda run() foarte importantă . Vom adăuga, de asemenea, o metodă de afișare a contoarelor pe ecran:


class ThreadDemo implements Runnable {

    int counter;
    ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();

    public void run() {
        counter++;

        if(threadLocalCounter.get() != null) {
            threadLocalCounter.set(threadLocalCounter.get() + 1);
        } else {
            threadLocalCounter.set(0);
        }
        printCounters();
    }

    public void printCounters(){
        System.out.println("Counter: " + counter);
        System.out.println("threadLocalCounter: " + threadLocalCounter.get());
    }
}

Cu fiecare cursă a clasei noastre, creștemtejgheavariabila apelează metoda get() pentru a obține date din variabila ThreadLocal . Dacă noul thread nu are date, atunci îl vom seta la 0. Dacă există date, îl vom crește cu unul. Și să scriem metoda noastră principală :


public static void main(String[] args) {
    ThreadDemo threadDemo = new ThreadDemo();

    Thread t1 = new Thread(threadDemo);
    Thread t2 = new Thread(threadDemo);
    Thread t3 = new Thread(threadDemo);

    t1.start();
    t2.start();
    t3.start();

}

Rulând clasa noastră, vedem că variabila ThreadLocal rămâne aceeași indiferent de firul care o accesează, dar numărul de fire crește.

Contor: 1
Contor: 2
Contor: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Proces terminat cu codul de ieșire 0

elimina()

Pentru a înțelege cum funcționează metoda de eliminare , vom schimba ușor codul din clasa ThreadDemo :


if(threadLocalCounter.get() != null) {
      threadLocalCounter.set(threadLocalCounter.get() + 1);
  } else {
      if (counter % 2 == 0) {
          threadLocalCounter.remove();
      } else {
          threadLocalCounter.set(0);
      }
  }

În acest cod, dacă contorul de fire este un număr par, atunci vom apela metoda remove() pe variabila noastră ThreadLocal . Rezultat:

Contor: 3
threadLocalCounter: 0
Contor: 2
threadLocalCounter: null
Contor: 1
threadLocalCounter: 0

Procesul încheiat cu codul de ieșire 0

Și aici vedem cu ușurință că variabila ThreadLocal din al doilea thread este null .

ThreadLocal.withInitial()

Această metodă creează o variabilă locală de fir.

Implementarea clasei ThreadDemo :


class ThreadDemo implements Runnable {

    int counter;
    ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 1);

    public void run() {
        counter++;
        printCounters();
    }

    public void printCounters(){
        System.out.println("Counter: " + counter);
        System.out.println("threadLocalCounter: " + threadLocalCounter.get());
    }
}

Și ne putem uita la rezultatul codului nostru:

Contor: 1
Contor: 2
Contor: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Proces terminat cu codul de ieșire 0

De ce ar trebui să folosim astfel de variabile?

ThreadLocal oferă o abstractizare asupra variabilelor locale în raport cu firul de execuție java.lang.Thread .

Variabilele ThreadLocal diferă de cele obișnuite prin faptul că fiecare fir are propria instanță a variabilei, inițializată individual, care este accesată prinmetodele get() și set() .

Fiecare fir, adică instanță a clasei Thread , are o hartă a variabilelor ThreadLocal asociate cu acesta. Cheile hărții sunt referințe la obiecte ThreadLocal , iar valorile sunt referințe la variabile ThreadLocal „dobândite”.

De ce clasa Random nu este potrivită pentru generarea de numere aleatorii în aplicații cu mai multe fire?

Folosim clasa Random pentru a obține numere aleatoare. Dar funcționează la fel de bine într-un mediu multithreaded? De fapt nu. Random nu este potrivit pentru mediile multithreaded, deoarece atunci când mai multe fire accesează o clasă în același timp, performanța are de suferit.

Pentru a rezolva această problemă, JDK 7 a introdus clasa java.util.concurrent.ThreadLocalRandom pentru a genera numere aleatorii într-un mediu cu mai multe fire. Este format din două clase: ThreadLocal și Random .

Numerele aleatoare primite de un fir sunt independente de alte fire, dar java.util.Random oferă numere aleatoare la nivel global. De asemenea, spre deosebire de Random , ThreadLocalRandom nu acceptă seeding explicit. În schimb, suprascrie metoda setSeed() moștenită de la Random , astfel încât să arunce întotdeauna o excepție UnsupportedOperationException atunci când este apelată.

Să ne uităm la metodele clasei ThreadLocalRandom :

Metodă Acțiune
ThreadLocalRandom current() Returnează ThreadLocalRandom al firului curent.
int următorul (int biți) Generează următorul număr pseudo-aleatoriu.
double nextDouble (dublu minim, dublu legat) Returnează un număr pseudoaleator dintr-o distribuție uniformă între minim (inclusiv) și limitat (exclusiv).
int nextInt(int minim, int legat) Returnează un număr pseudoaleator dintr-o distribuție uniformă între minim (inclusiv) și limitat (exclusiv).
lung următorulLung (n lung) Returnează un număr pseudoaleator dintr-o distribuție uniformă între 0 (inclusiv) și valoarea specificată (exclusiv).
long nextLong (long minim, long bound) Returnează un număr pseudoaleator dintr-o distribuție uniformă între minim (inclusiv) și limitat (exclusiv).
void setSeed (sămânță lungă) Aruncă UnsupportedOperationException . Acest generator nu acceptă însămânțarea.

Obținerea de numere aleatorii folosind ThreadLocalRandom.current()

ThreadLocalRandom este o combinație a claselor ThreadLocal și Random . Obține performanțe mai bune într-un mediu cu mai multe fire prin simpla evitare a oricărui acces concurent la instanțe ale clasei Random .

Să implementăm un exemplu care implică mai multe fire și să vedem că aplicația noastră face cu clasa ThreadLocalRandom :


import java.util.concurrent.ThreadLocalRandom;

class RandomNumbers extends Thread {

    public void run() {
        try {
            int bound = 100;
            int result = ThreadLocalRandom.current().nextInt(bound);
            System.out.println("Thread " + Thread.currentThread().getId() + " generated " + result);
        }
        catch (Exception e) {
            System.out.println("Exception");
        }
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

				for (int i = 0; i < 10; i++) {
            RandomNumbers randomNumbers = new RandomNumbers();
            randomNumbers.start();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Time taken: " + (endTime - startTime));
    }
}

Rezultatul programului nostru:

Timp luat: 1
Fir 17 generat 13
Fir 18 generat 41 Fir
16 generat 99 Thread
19 generat 25
Thread 23 generat 33
Thread 24 generat 21
Thread 15 generat 15 Thread 21 generat
28 Thread generat 297 Thread 3 3 0

Și acum să schimbăm clasa noastră RandomNumbers și să folosim Random în ea:


int result = new Random().nextInt(bound);
Timp luat: 5
Thread 20 generat 48
Thread 19 generat 57
Thread 18 generat 90
Thread 22 generat 43
Thread 24 generat 7
Thread 23 generat 63
Thread 15 generat 2
Thread 16 generat 40
Thread 21 generat
Thread 2 2917 generat

Ia-ti notite! În testele noastre, uneori rezultatele au fost aceleași și alteori au fost diferite. Dar dacă folosim mai multe fire (să zicem, 100), rezultatul va arăta astfel:

Aleatoriu — 19-25 ms
ThreadLocalRandom — 17-19 ms

În consecință, cu cât sunt mai multe fire de execuție în aplicația noastră, cu atât este mai mare performanța atunci când utilizați clasa Random într-un mediu cu mai multe fire.

Pentru a rezuma și a reitera diferențele dintre clasele Random și ThreadLocalRandom :

Aleatoriu ThreadLocalRandom
Dacă fire diferite folosesc aceeași instanță de Random , vor exista conflicte și performanța va avea de suferit. Nu există conflicte sau probleme, deoarece numerele aleatoare generate sunt locale pentru firul curent.
Utilizează o formulă congruențială liniară pentru a modifica valoarea inițială. Generatorul de numere aleatoare este inițializat folosind o sămânță generată intern.
Util în aplicațiile în care fiecare fir folosește propriul set de obiecte Random . Util în aplicațiile în care mai multe fire folosesc numere aleatorii în paralel în pool-urile de fire.
Aceasta este o clasă de părinți. Aceasta este o clasă pentru copii.