I denne leksjonen snakker vi generelt om å jobbe med java.lang.ThreadLocal<>- klassen og hvordan du bruker den i et flertrådsmiljø.

ThreadLocal - klassen brukes til å lagre variabler. Et særtrekk ved denne klassen er at den beholder en separat uavhengig kopi av en verdi for hver tråd som bruker den.

Når vi dykker dypere inn i klassens drift, kan vi forestille oss et kart som kartlegger tråder til verdier, hvorfra den gjeldende tråden tar den riktige verdien når den skal bruke den.

ThreadLocal klassekonstruktør

Konstruktør Handling
ThreadLocal() Oppretter en tom variabel i Java

Metoder

Metode Handling
få() Returnerer verdien til gjeldende tråds lokale variabel
sett() Angir verdien til den lokale variabelen for gjeldende tråd
fjerne() Fjerner verdien til den lokale variabelen for gjeldende tråd
ThreadLocal.withInitial() Ekstra fabrikkmetode som angir startverdien

klar()

La oss skrive et eksempel hvor vi lager to tellere. Den første, en ordinær variabel, vil være for å telle antall tråder. Den andre vil vi pakke inn i en ThreadLocal . Og vi får se hvordan de fungerer sammen. Først, la oss skrive en ThreadDemo- klasse som arver Runnable og inneholder dataene våre og den all-important run()- metoden. Vi legger også til en metode for å vise tellerne på skjermen:


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

For hvert løp i klassen vår øker vidiskvariabel kaller get() -metoden for å hente data fra ThreadLocal- variabelen. Hvis den nye tråden ikke har noen data, vil vi sette den til 0. Hvis det er data, vil vi øke den med én. Og la oss skrive hovedmetoden vår :


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

}

Når vi kjører klassen vår, ser vi at ThreadLocal- variabelen forblir den samme uavhengig av tråden som får tilgang til den, men antallet tråder vokser.

Counter: 1
Counter: 2
Counter: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Prosessen fullført med utgangskode 0

fjerne()

For å forstå hvordan fjerningsmetoden fungerer, vil vi bare endre koden litt i ThreadDemo -klassen:


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

I denne koden, hvis trådtelleren er et partall, vil vi kalle remove()- metoden på vår ThreadLocal- variabel. Resultat:

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

Prosessen er ferdig med utgangskode 0

Og her ser vi lett at ThreadLocal- variabelen i den andre tråden er null .

ThreadLocal.withInitial()

Denne metoden oppretter en trådlokal variabel.

Implementering av ThreadDemo- klassen:


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

Og vi kan se på resultatet av koden vår:

Counter: 1
Counter: 2
Counter: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Prosessen fullført med utgangskode 0

Hvorfor skal vi bruke slike variabler?

ThreadLocal gir en abstraksjon over lokale variabler i forhold til utførelsestråden java.lang.Thread .

ThreadLocal- variabler skiller seg fra vanlige ved at hver tråd har sin egen, individuelt initialiserte forekomst av variabelen, som er tilgjengelig viametodene get() og set() .

Hver tråd, dvs. forekomst av Thread -klassen, har et kart over ThreadLocal- variabler knyttet til seg. Kartets nøkler er referanser til ThreadLocal- objekter, og verdiene er referanser til "ervervede" ThreadLocal -variabler.

Hvorfor er ikke Random-klassen egnet for å generere tilfeldige tall i flertrådede applikasjoner?

Vi bruker Random- klassen for å få tilfeldige tall. Men fungerer det like bra i et flertrådsmiljø? Faktisk nei. Tilfeldig er ikke egnet for flertrådede miljøer, fordi når flere tråder får tilgang til en klasse samtidig, blir ytelsen dårligere.

For å løse dette problemet introduserte JDK 7 klassen java.util.concurrent.ThreadLocalRandom for å generere tilfeldige tall i et flertrådsmiljø. Den består av to klasser: ThreadLocal og Random .

De tilfeldige tallene som mottas av en tråd er uavhengige av andre tråder, men java.util.Random gir globalt tilfeldige tall. I motsetning til Random støtter ikke ThreadLocalRandom eksplisitt seeding. I stedet overstyrer den setSeed() -metoden som er arvet fra Random , slik at den alltid kaster en UnsupportedOperationException når den kalles.

La oss se på metodene til ThreadLocalRandom -klassen:

Metode Handling
ThreadLocalTilfeldig gjeldende() Returnerer ThreadLocalRandom for gjeldende tråd.
int neste(int bits) Genererer neste pseudo-tilfeldige tall.
dobbel nesteDobbel(dobbel minst, dobbel bundet) Returnerer et pseudotilfeldig tall fra en enhetlig fordeling mellom minst (inkluderende) og bundet (eksklusiv).
int nesteInt(minst, int bundet) Returnerer et pseudotilfeldig tall fra en enhetlig fordeling mellom minst (inkluderende) og bundet (eksklusiv).
lang nesteLang(lang n) Returnerer et pseudotilfeldig tall fra en enhetlig fordeling mellom 0 (inklusive) og den angitte verdien (eksklusiv).
long nextLong(lengst minst, lang bundet) Returnerer et pseudotilfeldig tall fra en enhetlig fordeling mellom minst (inkluderende) og bundet (eksklusiv).
void setSeed (langt frø) Kaster UnsupportedOperationException . Denne generatoren støtter ikke seeding.

Få tilfeldige tall ved å bruke ThreadLocalRandom.current()

ThreadLocalRandom er en kombinasjon av ThreadLocal og Random -klassene. Den oppnår bedre ytelse i et flertrådsmiljø ved ganske enkelt å unngå samtidig tilgang til forekomster av Random- klassen.

La oss implementere et eksempel som involverer flere tråder og se at applikasjonen vår gjør det med ThreadLocalRandom -klassen:


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

Resultatet av programmet vårt:

Tidsbruk: 1
Tråd 17 generert 13
Tråd 18 generert 41
Tråd 16 generert 99
Tråd 19 generert 25
Tråd 23 generert 33
Tråd 24 generert 21
Tråd 15 generert 15
Tråd 21 generert
28
Tråd generert 3 3

Og la oss nå endre RandomNumbers- klassen og bruke Random i den:


int result = new Random().nextInt(bound);
Tidsbruk: 5
Tråd 20 generert 48
Tråd 19 generert 57
Tråd 18 generert 90
Tråd 22 generert 43
Tråd 24 generert 7
Tråd 23 generert 63
Tråd 15 generert 2
Tråd 16 generert 40
Tråd 17 generert 29
Tråd 121

Ta notat! I våre tester var resultatene noen ganger de samme og noen ganger forskjellige. Men hvis vi bruker flere tråder (si 100), vil resultatet se slik ut:

Tilfeldig — 19–25 ms
ThreadLocalTilfeldig — 17–19 ms

Følgelig, jo flere tråder i applikasjonen vår, desto større ytelsestreff når du bruker Random- klassen i et flertrådsmiljø.

For å oppsummere og gjenta forskjellene mellom Random og ThreadLocalRandom- klassene:

Tilfeldig ThreadLocalTilfeldig
Hvis forskjellige tråder bruker samme forekomst av Random , vil det oppstå konflikter og ytelsen vil lide. Det er ingen konflikter eller problemer, fordi de genererte tilfeldige tallene er lokale for gjeldende tråd.
Bruker en lineær kongruensformel for å endre startverdien. Tilfeldig tallgeneratoren initialiseres ved hjelp av et internt generert frø.
Nyttig i applikasjoner der hver tråd bruker sitt eget sett med tilfeldige objekter. Nyttig i applikasjoner der flere tråder bruker tilfeldige tall parallelt i trådpooler.
Dette er en foreldreklasse. Dette er en barneklasse.