Dalam pelajaran ini, kita akan bercakap secara umum tentang bekerja dengan kelas java.lang.ThreadLocal<> dan cara menggunakannya dalam persekitaran berbilang benang.

Kelas ThreadLocal digunakan untuk menyimpan pembolehubah . Ciri tersendiri kelas ini ialah ia menyimpan salinan bebas yang berasingan bagi nilai untuk setiap utas yang menggunakannya.

Menyelidiki lebih mendalam ke dalam pengendalian kelas, kita boleh bayangkan Peta yang memetakan utas kepada nilai, dari mana utas semasa mengambil nilai yang sesuai apabila perlu menggunakannya.

Pembina kelas ThreadLocal

Pembina Tindakan
ThreadLocal() Mencipta pembolehubah kosong di Java

Kaedah

Kaedah Tindakan
dapatkan() Mengembalikan nilai pembolehubah tempatan utas semasa
set() Menetapkan nilai pembolehubah tempatan untuk utas semasa
keluarkan() Mengalih keluar nilai pembolehubah tempatan bagi benang semasa
ThreadLocal.withInitial() Kaedah kilang tambahan yang menetapkan nilai awal

dapatkan() & set()

Mari kita tulis contoh di mana kita mencipta dua pembilang. Yang pertama, pembolehubah biasa, adalah untuk mengira bilangan utas. Yang kedua kita akan bungkus dalam ThreadLocal . Dan kita akan lihat bagaimana mereka bekerjasama. Mula-mula, mari tulis kelas ThreadDemo yang mewarisi Runnable dan mengandungi data kami serta kaedah run() yang sangat penting . Kami juga akan menambah kaedah untuk memaparkan pembilang pada skrin:


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

Dengan setiap larian kelas kami, kami meningkatkankaunterpembolehubah panggil kaedah get() untuk mendapatkan data daripada pembolehubah ThreadLocal . Jika benang baru tiada data, maka kami akan menetapkannya kepada 0. Jika ada data, kami akan menambahnya satu. Dan mari tulis kaedah utama kami:


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

}

Menjalankan kelas kami, kami melihat bahawa pembolehubah ThreadLocal kekal sama tanpa mengira utas yang mengaksesnya, tetapi bilangan utas bertambah.

Kaunter: 1
Kaunter: 2
Kaunter: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Proses selesai dengan kod keluar 0

keluarkan()

Untuk memahami cara kaedah alih keluar berfungsi, kami hanya akan menukar sedikit kod dalam kelas ThreadDemo :


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

Dalam kod ini, jika pembilang benang ialah nombor genap, maka kami akan memanggil kaedah remove() pada pembolehubah ThreadLocal kami . Keputusan:

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

Proses selesai dengan kod keluar 0

Dan di sini kita dengan mudah melihat bahawa pembolehubah ThreadLocal dalam utas kedua adalah null .

ThreadLocal.withInitial()

Kaedah ini mencipta pembolehubah setempat-benang.

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

Dan kita boleh melihat hasil kod kami:

Kaunter: 1
Kaunter: 2
Kaunter: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Proses selesai dengan kod keluar 0

Mengapa kita perlu menggunakan pembolehubah sedemikian?

ThreadLocal menyediakan abstraksi ke atas pembolehubah tempatan berhubung dengan utas pelaksanaan java.lang.Thread .

Pembolehubah ThreadLocal berbeza daripada pembolehubah biasa kerana setiap utas mempunyai contoh pembolehubah yang dimulakan secara individu, yang diakses melalui kaedah get() dan set() .

Setiap thread, iaitu contoh kelas Thread , mempunyai peta pembolehubah ThreadLocal yang dikaitkan dengannya. Kekunci peta ialah rujukan kepada objek ThreadLocal dan nilai adalah rujukan kepada pembolehubah ThreadLocal "diperoleh" .

Mengapa kelas Rawak tidak sesuai untuk menjana nombor rawak dalam aplikasi berbilang benang?

Kami menggunakan kelas Rawak untuk mendapatkan nombor rawak. Tetapi adakah ia berfungsi dengan baik dalam persekitaran berbilang benang? Sebenarnya tidak. Rawak tidak sesuai untuk persekitaran berbilang benang, kerana apabila berbilang benang mengakses kelas pada masa yang sama, prestasi terjejas.

Untuk menangani masalah ini, JDK 7 memperkenalkan kelas java.util.concurrent.ThreadLocalRandom untuk menjana nombor rawak dalam persekitaran berbilang benang. Ia terdiri daripada dua kelas: ThreadLocal dan Random .

Nombor rawak yang diterima oleh satu utas adalah bebas daripada utas lain, tetapi java.util.Random menyediakan nombor rawak global. Juga, tidak seperti Random , ThreadLocalRandom tidak menyokong pembenihan eksplisit. Sebaliknya, ia mengatasi kaedah setSeed() yang diwarisi daripada Random , supaya ia sentiasa membuang UnsupportedOperationException apabila dipanggil.

Mari lihat kaedah kelas ThreadLocalRandom :

Kaedah Tindakan
ThreadLocalRandom semasa() Mengembalikan ThreadLocalRandom bagi utas semasa.
int seterusnya (bit int) Menghasilkan nombor pseudo-rawak seterusnya.
double nextDouble(double least, double bound) Mengembalikan nombor pseudorandom daripada taburan seragam antara paling sedikit (termasuk) dan terikat (eksklusif).
int nextInt(int least, int bound) Mengembalikan nombor pseudorandom daripada taburan seragam antara paling sedikit (termasuk) dan terikat (eksklusif).
lama seterusnyaLong(panjang n) Mengembalikan nombor pseudorandom daripada taburan seragam antara 0 (termasuk) dan nilai yang ditentukan (eksklusif).
panjang seterusnyaLong(panjang paling sedikit, panjang terikat) Mengembalikan nombor pseudorandom daripada taburan seragam antara paling sedikit (termasuk) dan terikat (eksklusif).
void setSeed(biji panjang) Membuang UnsupportedOperationException . Penjana ini tidak menyokong pembenihan.

Mendapatkan nombor rawak menggunakan ThreadLocalRandom.current()

ThreadLocalRandom ialah gabungan kelas ThreadLocal dan Random . Ia mencapai prestasi yang lebih baik dalam persekitaran berbilang benang dengan hanya mengelakkan sebarang akses serentak kepada contoh kelas Rawak .

Mari kita laksanakan contoh yang melibatkan berbilang benang dan lihat aplikasi kita lakukan dengan kelas 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));
    }
}

Hasil program kami:

Masa yang diambil: 1
Thread 17 dijana 13
Thread 18 dijana 41
Thread 16 dijana 99
Thread 19 dijana 25
Thread 23 dijana 33
Thread 24 dijana 21
Thread 15 dijana 15
Thread 21 dijana 28
Thread 22 dijana
32 dijana 3 Thread

Dan sekarang mari tukar kelas RandomNumbers kami dan gunakan Random di dalamnya:


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

Mengambil nota! Dalam ujian kami, kadangkala keputusannya sama dan kadangkala berbeza. Tetapi jika kita menggunakan lebih banyak benang (katakan, 100), hasilnya akan kelihatan seperti ini:

Rawak — 19-25 ms
ThreadLocalRandom — 17-19 ms

Sehubungan itu, lebih banyak utas dalam aplikasi kami, lebih besar prestasi yang dicapai apabila menggunakan kelas Rawak dalam persekitaran berbilang benang.

Untuk meringkaskan dan mengulangi perbezaan antara kelas Random dan ThreadLocalRandom :

rawak ThreadLocalRandom
Jika urutan yang berbeza menggunakan contoh Random yang sama , akan berlaku konflik dan prestasi akan terjejas. Tiada konflik atau masalah, kerana nombor rawak yang dijana adalah setempat kepada urutan semasa.
Menggunakan formula kongruen linear untuk menukar nilai awal. Penjana nombor rawak dimulakan menggunakan benih yang dijana secara dalaman.
Berguna dalam aplikasi di mana setiap utas menggunakan set objek Rawaknya sendiri . Berguna dalam aplikasi yang berbilang benang menggunakan nombor rawak secara selari dalam kumpulan benang.
Ini adalah kelas ibu bapa. Ini adalah kelas kanak-kanak.