I den här lektionen kommer vi att prata allmänt om att arbeta med klassen java.lang.ThreadLocal<> och hur man använder den i en flertrådsmiljö.

Klassen ThreadLocal används för att lagra variabler. En utmärkande egenskap för denna klass är att den behåller en separat oberoende kopia av ett värde för varje tråd som använder den.

När vi fördjupar oss i klassens funktion kan vi föreställa oss en karta som mappar trådar till värden, från vilken den aktuella tråden tar rätt värde när den behöver använda den.

ThreadLocal klasskonstruktör

Konstruktör Handling
ThreadLocal() Skapar en tom variabel i Java

Metoder

Metod Handling
skaffa sig() Returnerar värdet för den aktuella trådens lokala variabel
uppsättning() Ställer in värdet på den lokala variabeln för den aktuella tråden
avlägsna() Tar bort värdet på den lokala variabeln för den aktuella tråden
ThreadLocal.withInitial() Ytterligare fabriksmetod som anger startvärdet

get() & set()

Låt oss skriva ett exempel där vi skapar två räknare. Den första, en vanlig variabel, kommer att vara för att räkna antalet trådar. Den andra kommer vi att slå in i en ThreadLocal . Och vi får se hur de fungerar tillsammans. Låt oss först skriva en ThreadDemo- klass som ärver Runnable och som innehåller vår data och den all-important run()- metoden. Vi kommer också att lägga till en metod för att visa räknarna 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 varje körning i vår klass ökar vidiskenvariabel anropar metoden get() för att hämta data från variabeln ThreadLocal . Om den nya tråden inte har några data kommer vi att sätta den till 0. Om det finns data kommer vi att öka den med en. Och låt oss skriva vår huvudmetod :


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ör vår klass ser vi att ThreadLocal -variabeln förblir densamma oavsett vilken tråd som kommer åt den, men antalet trådar växer.

Räknare: 1
Räknare: 2
Räknare: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Processen avslutad med utgångskod 0

avlägsna()

För att förstå hur borttagningsmetoden fungerar kommer vi bara att ändra koden något i klassen ThreadDemo :


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

I den här koden, om trådräknaren är ett jämnt tal, anropar vi metoden remove() på vår ThreadLocal -variabel. Resultat:

Räknare: 3
threadLocalCounter: 0
Räknare: 2
threadLocalCounter: null
Räknare: 1
threadLocalCounter: 0

Processen avslutad med utgångskod 0

Och här ser vi lätt att ThreadLocal -variabeln i den andra tråden är null .

ThreadLocal.withInitial()

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

Och vi kan titta på resultatet av vår kod:

Räknare: 1
Räknare: 2
Räknare: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Processen avslutad med utgångskod 0

Varför ska vi använda sådana variabler?

ThreadLocal tillhandahåller en abstraktion över lokala variabler i relation till exekveringstråden java.lang.Thread .

ThreadLocal- variabler skiljer sig från vanliga genom att varje tråd har sin egen, individuellt initierade instans av variabeln, som nås viametoderna get() och set() .

Varje tråd, dvs instans av klassen Thread , har en karta över ThreadLocal -variabler kopplade till sig. Kartans nycklar är referenser till ThreadLocal- objekt, och värdena är referenser till "förvärvade" ThreadLocal -variabler.

Varför är klassen Random inte lämplig för att generera slumptal i flertrådade applikationer?

Vi använder klassen Random för att få slumptal. Men fungerar det lika bra i en multitrådad miljö? Faktiskt nej. Random är inte lämpligt för flertrådade miljöer, för när flera trådar får åtkomst till en klass samtidigt blir prestandan lidande.

För att lösa detta problem introducerade JDK 7 klassen java.util.concurrent.ThreadLocalRandom för att generera slumpmässiga tal i en flertrådsmiljö. Den består av två klasser: ThreadLocal och Random .

De slumptal som tas emot av en tråd är oberoende av andra trådar, men java.util.Random tillhandahåller globalt slumptal. Till skillnad från Random stöder inte ThreadLocalRandom explicit sådd . Istället åsidosätter den metoden setSeed() som ärvts från Random , så att den alltid kastar en UnsupportedOperationException när den anropas.

Låt oss titta på metoderna för klassen ThreadLocalRandom :

Metod Handling
ThreadLocalRandom current() Returnerar ThreadLocalRandom för den aktuella tråden.
int nästa(int bitar) Genererar nästa pseudoslumptal.
dubbel nästaDubbel(dubbel minst, dubbelbunden) Returnerar ett pseudoslumptal från en enhetlig fördelning mellan minst (inklusive) och bundet (exklusivt).
int nästaInt(minst, int bunden) Returnerar ett pseudoslumptal från en enhetlig fördelning mellan minst (inklusive) och bundet (exklusivt).
long nextLong(long n) Returnerar ett pseudoslumptal från en enhetlig fördelning mellan 0 (inklusive) och det angivna värdet (exklusivt).
long nextLong(lång minst, lång bunden) Returnerar ett pseudoslumptal från en enhetlig fördelning mellan minst (inklusive) och bundet (exklusivt).
void setSeed (långt frö) Kastar UnsupportedOperationException . Denna generator stöder inte sådd.

Få slumptal med ThreadLocalRandom.current()

ThreadLocalRandom är en kombination avklasserna ThreadLocal och Random . Den uppnår bättre prestanda i en flertrådad miljö genom att helt enkelt undvika all samtidig åtkomst till instanser avklassen Random .

Låt oss implementera ett exempel som involverar flera trådar och se vår applikation gör med klassen 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));
    }
}

Resultatet av vårt program:

Tid: 1
Tråd 17 genererad 13
Tråd 18 genererad 41
Tråd 16 genererad 99
Tråd 19 genererad 25
Tråd 23 genererad 33
Tråd 24 genererad 21
Tråd 15 genererad 15
Tråd 21 genererad
28
Tråd 22 genererad 0

Och låt oss nu ändra vår RandomNumbers- klass och använda Random i den:


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

Notera! I våra tester var resultaten ibland desamma och ibland olika. Men om vi använder fler trådar (säg 100), kommer resultatet att se ut så här:

Slumpmässigt — 19-25 ms
ThreadLocal Random — 17-19 ms

Följaktligen, ju fler trådar i vår applikation, desto större prestandaträff när du använder klassen Random i en flertrådad miljö.

För att sammanfatta och upprepa skillnaderna mellan klasserna Random och ThreadLocalRandom :

Slumpmässig ThreadLocalRandom
Om olika trådar använder samma instans av Random kommer det att uppstå konflikter och prestandan blir lidande. Det finns inga konflikter eller problem, eftersom de genererade slumptalen är lokala för den aktuella tråden.
Använder en linjär kongruensformel för att ändra det initiala värdet. Slumptalsgeneratorn initieras med hjälp av ett internt genererat frö.
Användbar i applikationer där varje tråd använder sin egen uppsättning slumpmässiga objekt. Användbar i applikationer där flera trådar använder slumptal parallellt i trådpooler.
Det här är en föräldraklass. Det här är en barnklass.