I denne lektion vil vi generelt tale om at arbejde med klassen java.lang.ThreadLocal<> og hvordan man bruger den i et flertrådsmiljø.

ThreadLocal - klassen bruges til at gemme variabler. Et karakteristisk træk ved denne klasse er, at den beholder en separat uafhængig kopi af en værdi for hver tråd, der bruger den.

Når vi dykker dybere ned i klassens drift, kan vi forestille os et kort , der kortlægger tråde til værdier, hvorfra den aktuelle tråd tager den passende værdi, når den skal bruge den.

ThreadLocal klassekonstruktør

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

Metoder

Metode Handling
få() Returnerer værdien af ​​den aktuelle tråds lokale variabel
sæt() Indstiller værdien af ​​den lokale variabel for den aktuelle tråd
fjerne() Fjerner værdien af ​​den lokale variabel i den aktuelle tråd
ThreadLocal.withInitial() Yderligere fabriksmetode, der indstiller startværdien

get() & sæt()

Lad os skrive et eksempel, hvor vi laver to tællere. Den første, en almindelig variabel, vil være til at tælle antallet af tråde. Den anden vil vi pakke ind i en ThreadLocal . Og vi vil se, hvordan de arbejder sammen. Lad os først skrive en ThreadDemo- klasse, der arver Runnable og indeholder vores data og den altafgørende run()- metode. Vi tilføjer også en metode til at vise tællerne på skærmen:


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

Med hver løbetur i vores klasse øger vitællervariabel kalder get() -metoden for at hente data fra ThreadLocal- variablen. Hvis den nye tråd ikke har nogen data, så sætter vi den til 0. Hvis der er data, øger vi den med én. Og lad os skrive vores hovedmetode :


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 kører vores klasse, ser vi, at ThreadLocal- variablen forbliver den samme uanset den tråd, der får adgang til den, men antallet af tråde vokser.

Tæller: 1
Tæller: 2
Tæller: 3
trådLokaltæller: 0
trådLokaltæller: 0
trådLokaltæller: 0

Processen afsluttet med exitkode 0

fjerne()

For at forstå, hvordan fjernelsesmetoden virker, ændrer vi blot en smule koden 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 kode, hvis trådtælleren er et lige tal, kalder vi metoden remove() på vores ThreadLocal- variabel. Resultat:

Tæller: 3
threadLocalCounter: 0
Tæller: 2
threadLocalCounter: null
Tæller: 1
threadLocalCounter: 0

Processen afsluttet med exitkode 0

Og her kan vi nemt se, at ThreadLocal- variablen i den anden tråd er null .

ThreadLocal.withInitial()

Denne metode opretter en tråd-lokal variabel.

Implementering af 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 af vores kode:

Tæller: 1
Tæller: 2
Tæller: 3
trådLokaltæller: 1
trådLokaltæller: 1
trådLokaltæller: 1

Processen afsluttet med exitkode 0

Hvorfor skal vi bruge sådanne variabler?

ThreadLocal giver en abstraktion over lokale variable i forhold til udførelsestråden java.lang.Thread .

ThreadLocal variabler adskiller sig fra almindelige ved, at hver tråd har sin egen, individuelt initialiserede forekomst af variablen, som tilgås via get () og set() metoderne.

Hver tråd, dvs. forekomst af Thread -klassen, har et kort over ThreadLocal- variabler tilknyttet. Kortets nøgler er referencer til ThreadLocal- objekter, og værdierne er referencer til "erhvervede" ThreadLocal -variabler.

Hvorfor er Random-klassen ikke egnet til at generere tilfældige tal i flertrådede applikationer?

Vi bruger klassen Random til at få tilfældige tal. Men fungerer det lige så godt i et multithreaded miljø? Faktisk nej. Tilfældig er ikke egnet til flertrådede miljøer, for når flere tråde får adgang til en klasse på samme tid, lider ydeevnen.

For at løse dette problem introducerede JDK 7 klassen java.util.concurrent.ThreadLocalRandom for at generere tilfældige tal i et flertrådsmiljø. Den består af to klasser: ThreadLocal og Random .

De tilfældige tal modtaget af en tråd er uafhængige af andre tråde, men java.util.Random giver globalt tilfældige tal. I modsætning til Random understøtter ThreadLocalRandom heller ikke eksplicit seeding. I stedet tilsidesætter den setSeed()- metoden, der er arvet fra Random , så den altid kaster en UnsupportedOperationException , når den kaldes.

Lad os se på metoderne i klassen ThreadLocalRandom :

Metode Handling
ThreadLocalRandom current() Returnerer ThreadLocalRandom for den aktuelle tråd.
int næste(int bits) Genererer det næste pseudo-tilfældige tal.
dobbelt næsteDobbelt(dobbelt mindst, dobbelt bundet) Returnerer et pseudotilfældigt tal fra en ensartet fordeling mellem mindste (inklusive) og bundet (eksklusiv).
int næsteInt(mindst, int bundet) Returnerer et pseudotilfældigt tal fra en ensartet fordeling mellem mindste (inklusive) og bundet (eksklusiv).
lang næsteLang(lang n) Returnerer et pseudotilfældigt tal fra en ensartet fordeling mellem 0 (inklusive) og den angivne værdi (eksklusiv).
lang næsteLang(lang mindst, lang bundet) Returnerer et pseudotilfældigt tal fra en ensartet fordeling mellem mindste (inklusive) og bundet (eksklusiv).
void setSeed (langt frø) Kaster UnsupportedOperationException . Denne generator understøtter ikke seeding.

Hent tilfældige tal ved hjælp af ThreadLocalRandom.current()

ThreadLocalRandom er en kombination af klasserne ThreadLocal og Random . Det opnår bedre ydeevne i et multithreaded-miljø ved blot at undgå enhver samtidig adgang til forekomster af Random- klassen.

Lad os implementere et eksempel, der involverer flere tråde, og se vores applikation gø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 af vores program:

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

Og lad os nu ændre vores RandomNumbers- klasse og bruge Random i den:


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

Tage til efterretning! I vores test var resultaterne nogle gange de samme, og nogle gange var de forskellige. Men hvis vi bruger flere tråde (f.eks. 100), vil resultatet se sådan ud:

Tilfældig — 19-25 ms
ThreadLocal Random — 17-19 ms

Følgelig, jo flere tråde i vores applikation, desto større ydelseshit, når du bruger Random- klassen i et multithreaded-miljø.

For at opsummere og gentage forskellene mellem klasserne Random og ThreadLocalRandom :

Tilfældig ThreadLocalTilfældig
Hvis forskellige tråde bruger den samme forekomst af Random , vil der være konflikter, og ydeevnen vil lide. Der er ingen konflikter eller problemer, fordi de genererede tilfældige tal er lokale for den aktuelle tråd.
Bruger en lineær kongruentiel formel til at ændre startværdien. Generatoren af ​​tilfældige tal initialiseres ved hjælp af et internt genereret frø.
Nyttig i applikationer, hvor hver tråd bruger sit eget sæt af tilfældige objekter. Nyttigt i applikationer, hvor flere tråde bruger tilfældige tal parallelt i trådpuljer.
Dette er en forældreklasse. Dette er en børneklasse.