Sa araling ito, karaniwang pag-uusapan natin ang tungkol sa pagtatrabaho sa klase ng java.lang.ThreadLocal<> at kung paano ito gamitin sa isang multithreaded na kapaligiran.

Ang ThreadLocal class ay ginagamit upang mag-imbak ng mga variable. Ang isang natatanging tampok ng klase na ito ay pinapanatili nito ang isang hiwalay na independiyenteng kopya ng isang halaga para sa bawat thread na gumagamit nito.

Sa mas malalim na pagsisiyasat sa pagpapatakbo ng klase, maiisip natin ang isang Map na nagmamapa ng mga thread sa mga value, kung saan kinukuha ng kasalukuyang thread ang naaangkop na halaga kapag kailangan itong gamitin.

Tagabuo ng klase ng ThreadLocal

Tagabuo Aksyon
ThreadLocal() Lumilikha ng walang laman na variable sa Java

Paraan

Pamamaraan Aksyon
kumuha () Ibinabalik ang halaga ng lokal na variable ng kasalukuyang thread
itakda() Itinatakda ang halaga ng lokal na variable para sa kasalukuyang thread
tanggalin() Tinatanggal ang halaga ng lokal na variable ng kasalukuyang thread
ThreadLocal.withInitial() Karagdagang paraan ng factory na nagtatakda ng paunang halaga

get() & set()

Sumulat tayo ng isang halimbawa kung saan gumagawa tayo ng dalawang counter. Ang una, isang ordinaryong variable, ay para sa pagbibilang ng bilang ng mga thread. Ang pangalawa ay ibalot namin sa isang ThreadLocal . At makikita natin kung paano sila nagtutulungan. Una, magsulat tayo ng klase ng ThreadDemo na nagmamana ng Runnable at naglalaman ng aming data at ang pinakamahalagang run() na pamamaraan. Magdaragdag din kami ng paraan para sa pagpapakita ng mga counter sa screen:


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

Sa bawat pagtakbo ng aming klase, nadadagdagan namin angcountervariable na tawagan ang get() method para makakuha ng data mula sa ThreadLocal variable. Kung ang bagong thread ay walang data, pagkatapos ay itatakda namin ito sa 0. Kung mayroong data, dagdagan namin ito ng isa. At isulat natin ang aming pangunahing pamamaraan:


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

}

Sa pagpapatakbo ng aming klase, nakikita namin na ang ThreadLocal variable ay nananatiling pareho anuman ang thread na nag-a-access dito, ngunit ang bilang ng mga thread ay lumalaki.

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

Natapos ang proseso gamit ang exit code 0

tanggalin()

Upang maunawaan kung paano gumagana ang paraan ng pag-alis , babaguhin lang namin nang bahagya ang code sa klase ng ThreadDemo :


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

Sa code na ito, kung ang thread counter ay even number, tatawagin namin ang remove() na paraan sa aming ThreadLocal variable. Resulta:

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

Natapos ang proseso gamit ang exit code 0

At dito madali nating makita na ang ThreadLocal variable sa pangalawang thread ay null .

ThreadLocal.withInitial()

Lumilikha ang pamamaraang ito ng thread-local na variable.

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

At maaari naming tingnan ang resulta ng aming code:

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

Natapos ang proseso gamit ang exit code 0

Bakit dapat nating gamitin ang gayong mga variable?

Nagbibigay ang ThreadLocal ng abstraction sa mga lokal na variable na may kaugnayan sa thread ng execution java.lang.Thread .

Ang mga variable ng ThreadLocal ay naiiba sa mga karaniwan dahil ang bawat thread ay may sariling, indibidwal na nasimulan na halimbawa ng variable, na naa-access sa pamamagitan ng get() at set() na mga pamamaraan.

Ang bawat thread, ie instance ng Thread class, ay may mapa ng ThreadLocal variable na nauugnay dito. Ang mga susi ng mapa ay mga sanggunian sa ThreadLocal na mga bagay, at ang mga halaga ay mga sanggunian sa "nakuha" na mga variable ng ThreadLocal .

Bakit hindi angkop ang Random na klase para sa pagbuo ng mga random na numero sa mga multithreaded na application?

Ginagamit namin ang Random na klase upang makakuha ng mga random na numero. Ngunit gumagana ba ito nang maayos sa isang multithreaded na kapaligiran? Sa totoo lang hindi. Ang random ay hindi angkop para sa mga multithreaded na kapaligiran, dahil kapag maraming mga thread ang nag-access sa isang klase nang sabay-sabay, naghihirap ang pagganap.

Upang matugunan ang problemang ito, ipinakilala ng JDK 7 ang java.util.concurrent.ThreadLocalRandom na klase upang bumuo ng mga random na numero sa isang multithreaded na kapaligiran. Binubuo ito ng dalawang klase: ThreadLocal at Random .

Ang mga random na numero na natanggap ng isang thread ay independiyente sa iba pang mga thread, ngunit ang java.util.Random ay nagbibigay ng mga global na random na numero. Gayundin, hindi tulad ng Random , hindi sinusuportahan ng ThreadLocalRandom ang tahasang seeding. Sa halip, ino-override nito ang setSeed() na pamamaraan na minana mula sa Random , para palagi itong naghagis ng UnsupportedOperationException kapag tinawag.

Tingnan natin ang mga pamamaraan ng klase ng ThreadLocalRandom :

Pamamaraan Aksyon
ThreadLocalRandom kasalukuyang() Ibinabalik ang ThreadLocalRandom ng kasalukuyang thread.
int susunod (int bits) Bumubuo ng susunod na pseudo-random na numero.
double nextDouble(double least, double bound) Nagbabalik ng pseudorandom na numero mula sa pare-parehong pamamahagi sa pagitan ng hindi bababa sa (kabilang) at nakatali (eksklusibo).
int nextInt(int least, int bound) Nagbabalik ng pseudorandom na numero mula sa pare-parehong pamamahagi sa pagitan ng hindi bababa sa (kabilang) at nakatali (eksklusibo).
mahabang susunodMahaba(mahaba n) Nagbabalik ng pseudorandom na numero mula sa isang pare-parehong pamamahagi sa pagitan ng 0 (kabilang) at ang tinukoy na halaga (eksklusibo).
mahaba susunodMahaba(mahaba pinakamababa, mahabang bound) Nagbabalik ng pseudorandom na numero mula sa pare-parehong pamamahagi sa pagitan ng hindi bababa sa (kabilang) at nakatali (eksklusibo).
void setSeed(mahabang buto) Itinapon ang UnsupportedOperationException . Ang generator na ito ay hindi sumusuporta sa seeding.

Pagkuha ng mga random na numero gamit ang ThreadLocalRandom.current()

Ang ThreadLocalRandom ay isang kumbinasyon ng mga klase ng ThreadLocal at Random . Nakakamit nito ang mas mahusay na pagganap sa isang multithreaded na kapaligiran sa pamamagitan lamang ng pag-iwas sa anumang kasabay na pag-access sa mga pagkakataon ng Random na klase.

Magpatupad tayo ng isang halimbawa na kinasasangkutan ng maraming mga thread at tingnan ang ginagawa ng aming application sa klase ng 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));
    }
}

Resulta ng aming programa:

Oras na kinuha: 1
Thread 17 nabuo 13
Thread 18 nabuo 41
Thread 16 nabuo 99
Thread 19 nabuo 25
Thread 23 nabuo 33
Thread 24 nabuo 21
Thread 15 nabuo 15
Thread 21 nabuo 28
Thread 22
nabuo 307 Thread

At ngayon baguhin natin ang aming RandomNumbers na klase at gamitin ang Random dito:


int result = new Random().nextInt(bound);
Nabuo ang oras: 5
Thread 20 nabuo 48
Thread 19 nabuo 57
Thread 18 nabuo 90
Thread 22 nabuo 43
Thread 24 nabuo 7
Thread 23 nabuo 63
Thread 15 nabuo 2
Thread 16 nabuo 40
Thread 17 nabuo 29
Thread 121

Tandaan! Sa aming mga pagsusulit, kung minsan ang mga resulta ay pareho at kung minsan ay iba. Ngunit kung gagamit tayo ng higit pang mga thread (sabihin, 100), ang magiging resulta ay ganito:

Random — 19-25 ms
ThreadLocalRandom — 17-19 ms

Alinsunod dito, mas maraming thread sa aming application, mas malaki ang performance na hit kapag ginagamit ang Random na klase sa isang multithreaded na kapaligiran.

Upang buod at ulitin ang mga pagkakaiba sa pagitan ng Random at ThreadLocalRandom na mga klase:

Random ThreadLocalRandom
Kung ang iba't ibang thread ay gumagamit ng parehong instance ng Random , magkakaroon ng mga salungatan at maghihirap ang performance. Walang mga salungatan o problema, dahil ang mga nabuong random na numero ay lokal sa kasalukuyang thread.
Gumagamit ng linear congruential formula upang baguhin ang paunang halaga. Ang random number generator ay sinisimulan gamit ang isang panloob na nabuong binhi.
Kapaki-pakinabang sa mga application kung saan ang bawat thread ay gumagamit ng sarili nitong hanay ng mga Random na bagay. Kapaki-pakinabang sa mga application kung saan maraming mga thread ang gumagamit ng mga random na numero nang magkatulad sa mga thread pool.
Ito ay isang klase ng magulang. Ito ay isang klase ng bata.