In deze les gaan we in het algemeen in op het werken met de klasse java.lang.ThreadLocal<> en hoe deze te gebruiken in een omgeving met meerdere threads.

De klasse ThreadLocal wordt gebruikt om variabelen op te slaan. Een onderscheidend kenmerk van deze klasse is dat het een afzonderlijke onafhankelijke kopie van een waarde bewaart voor elke thread die er gebruik van maakt.

Als we dieper ingaan op de werking van de klasse, kunnen we ons een kaart voorstellen die threads toewijst aan waarden, waarvan de huidige thread de juiste waarde overneemt wanneer deze nodig is.

ThreadLocal-klasseconstructor

Constructeur Actie
DiscussieLokaal() Creëert een lege variabele in Java

methoden

Methode Actie
krijgen() Retourneert de waarde van de lokale variabele van de huidige thread
set() Stelt de waarde in van de lokale variabele voor de huidige thread
verwijderen() Verwijdert de waarde van de lokale variabele van de huidige thread
ThreadLocal.withInitial() Aanvullende fabrieksmethode die de beginwaarde instelt

Maak je klaar()

Laten we een voorbeeld schrijven waarin we twee tellers maken. De eerste, een gewone variabele, is voor het tellen van het aantal threads. De tweede zullen we verpakken in een ThreadLocal . En we zullen zien hoe ze samenwerken. Laten we eerst een ThreadDemo- klasse schrijven die Runnable erft en onze gegevens en de allerbelangrijkste methode run() bevat . We voegen ook een methode toe om de tellers op het scherm weer te geven:


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());
    }
}

Met elke run van onze klasse verhogen we debalievariabele roep de methode get() aan om gegevens op te halen uit de variabele ThreadLocal . Als de nieuwe thread geen gegevens heeft, zetten we deze op 0. Als er gegevens zijn, verhogen we deze met één. En laten we onze hoofdmethode schrijven :


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();

}

Als we onze klasse uitvoeren, zien we dat de variabele ThreadLocal hetzelfde blijft, ongeacht de thread die er toegang toe heeft, maar het aantal threads groeit.

Teller: 1
Teller: 2
Teller: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Proces voltooid met afsluitcode 0

verwijderen()

Om te begrijpen hoe de verwijdermethode werkt, zullen we de code in de ThreadDemo- klasse enigszins wijzigen :


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

Als de threadteller in deze code een even getal is, roepen we de methode remove() op onze ThreadLocal- variabele aan. Resultaat:

Teller: 3
threadLocalCounter: 0
Teller: 2
threadLocalCounter: null
Teller: 1
threadLocalCounter: 0

Proces voltooid met afsluitcode 0

En hier zien we gemakkelijk dat de variabele ThreadLocal in de tweede thread null is .

ThreadLocal.withInitial()

Deze methode maakt een thread-local variabele aan.

Implementatie van de ThreadDemo- klasse:


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());
    }
}

En we kunnen kijken naar het resultaat van onze code:

Teller: 1
Teller: 2
Teller: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Proces voltooid met afsluitcode 0

Waarom zouden we dergelijke variabelen gebruiken?

ThreadLocal biedt een abstractie van lokale variabelen in relatie tot de uitvoeringsthread java.lang.Thread .

ThreadLocal- variabelen verschillen van gewone variabelen doordat elke thread zijn eigen, individueel geïnitialiseerde instantie van de variabele heeft, die toegankelijk is via demethoden get() en set() .

Aan elke thread, dwz instantie van de klasse Thread , is een kaart gekoppeld met ThreadLocal- variabelen. De sleutels van de map zijn verwijzingen naar ThreadLocal- objecten en de waarden zijn verwijzingen naar "verworven" ThreadLocal -variabelen.

Waarom is de klasse Random niet geschikt voor het genereren van willekeurige getallen in toepassingen met meerdere threads?

We gebruiken de klasse Random om willekeurige getallen te krijgen. Maar werkt het net zo goed in een multithreaded omgeving? Eigenlijk niet. Willekeurig is niet geschikt voor omgevingen met meerdere threads, omdat wanneer meerdere threads tegelijkertijd toegang hebben tot een klasse, de prestaties eronder lijden.

Om dit probleem aan te pakken, introduceerde JDK 7 de klasse java.util.concurrent.ThreadLocalRandom om willekeurige getallen te genereren in een omgeving met meerdere threads. Het bestaat uit twee klassen: ThreadLocal en Random .

De willekeurige nummers die door één thread worden ontvangen, zijn onafhankelijk van andere threads, maar java.util.Random biedt wereldwijd willekeurige nummers. Ook ondersteunt ThreadLocalRandom , in tegenstelling tot Random , geen expliciete seeding. In plaats daarvan overschrijft het de methode setSeed() die is geërfd van Random , zodat het altijd een UnsupportedOperationException genereert wanneer het wordt aangeroepen.

Laten we eens kijken naar de methoden van de klasse ThreadLocalRandom :

Methode Actie
ThreadLocalWillekeurige stroom() Retourneert de ThreadLocalRandom van de huidige thread.
int volgende (int bits) Genereert het volgende pseudo-willekeurige getal.
double nextDouble (dubbel minste, dubbele grens) Retourneert een pseudowillekeurig getal uit een uniforme verdeling tussen minst (inclusief) en gebonden (exclusief).
int nextInt(int minste, int gebonden) Retourneert een pseudowillekeurig getal uit een uniforme verdeling tussen minst (inclusief) en gebonden (exclusief).
lange volgendeLang(lange n) Retourneert een pseudowillekeurig getal uit een uniforme verdeling tussen 0 (inclusief) en de opgegeven waarde (exclusief).
long nextLong(lang minst, lang gebonden) Retourneert een pseudowillekeurig getal uit een uniforme verdeling tussen minst (inclusief) en gebonden (exclusief).
leegte setSeed (lang zaad) Gooit UnsupportedOperationException . Deze generator ondersteunt seeding niet.

Willekeurige getallen krijgen met ThreadLocalRandom.current()

ThreadLocalRandom is een combinatie van de klassen ThreadLocal en Random . Het bereikt betere prestaties in een omgeving met meerdere threads door eenvoudig gelijktijdige toegang tot instanties van de klasse Random te vermijden .

Laten we een voorbeeld implementeren met meerdere threads en zien hoe onze applicatie dat doet met de ThreadLocalRandom- klasse:


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));
    }
}

Resultaat van ons programma:

Benodigde tijd: 1
Thread 17 gegenereerd 13
Thread 18 gegenereerd 41
Thread 16 gegenereerd 99
Thread 19 gegenereerd 25
Thread 23 gegenereerd 33
Thread 24 gegenereerd 21
Thread 15 gegenereerd 15
Thread 21 gegenereerd 28
Thread 22 gegenereerd 97
Thread 20 gegenereerd 33

En laten we nu onze klasse RandomNumbers wijzigen en er Willekeurig in gebruiken:


int result = new Random().nextInt(bound);
Benodigde tijd: 5
Thread 20 gegenereerd 48
Thread 19 gegenereerd 57
Thread 18 gegenereerd 90
Thread 22 gegenereerd 43
Thread 24 gegenereerd 7
Thread 23 gegenereerd 63
Thread 15 gegenereerd 2
Thread 16 gegenereerd 40
Thread 17 gegenereerd 29
Thread 21 gegenereerd 12

Maak een notitie! In onze tests waren de resultaten soms hetzelfde en soms verschillend. Maar als we meer threads gebruiken (bijvoorbeeld 100), ziet het resultaat er als volgt uit:

Willekeurig — 19-25 ms
ThreadLocalRandom — 17-19 ms

Dienovereenkomstig, hoe meer threads in onze applicatie, hoe groter de prestatiehit bij gebruik van de klasse Random in een omgeving met meerdere threads.

Om de verschillen tussen de klassen Random en ThreadLocalRandom samen te vatten en te herhalen :

Willekeurig ThreadLocalRandom
Als verschillende threads dezelfde instantie van Random gebruiken , zullen er conflicten zijn en zullen de prestaties eronder lijden. Er zijn geen conflicten of problemen, omdat de gegenereerde willekeurige getallen lokaal zijn voor de huidige thread.
Gebruikt een lineaire congruentiële formule om de beginwaarde te wijzigen. De random number generator wordt geïnitialiseerd met behulp van een intern gegenereerde seed.
Handig in toepassingen waarbij elke thread zijn eigen set willekeurige objecten gebruikt. Handig in toepassingen waarbij meerdere threads willekeurige getallen parallel gebruiken in threadpools.
Dit is een ouderklas. Dit is een kinderles.