Dalam pelajaran ini, kita akan berbicara secara umum tentang bekerja dengan kelas java.lang.ThreadLocal<> dan bagaimana menggunakannya dalam lingkungan multithreaded.

Kelas ThreadLocal digunakan untuk menyimpan variabel. Fitur khas dari kelas ini adalah bahwa ia menyimpan salinan nilai yang terpisah untuk setiap utas yang menggunakannya.

Menggali lebih dalam operasi kelas, kita bisa membayangkan Peta yang memetakan utas ke nilai, dari mana utas saat ini mengambil nilai yang sesuai saat perlu menggunakannya.

konstruktor kelas ThreadLocal

Konstruktor Tindakan
ThreadLokal() Membuat variabel kosong di Java

Metode

metode Tindakan
mendapatkan() Mengembalikan nilai variabel lokal utas saat ini
mengatur() Menetapkan nilai variabel lokal untuk utas saat ini
menghapus() Menghapus nilai variabel lokal dari utas saat ini
ThreadLocal.withInitial() Metode pabrik tambahan yang menetapkan nilai awal

dapatkan() & atur()

Mari kita tulis contoh di mana kita membuat dua penghitung. Yang pertama, variabel biasa, untuk menghitung jumlah utas. Yang kedua kita akan membungkus ThreadLocal . Dan kita akan melihat bagaimana mereka bekerja sama. Pertama, mari kita tulis kelas ThreadDemo yang mewarisi Runnable dan berisi data kita dan metode run() yang sangat penting . Kami juga akan menambahkan metode untuk menampilkan penghitung di layar:


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 menjalankan kelas kami, kami meningkatkanmenangkalvariabel memanggil metode get() untuk mendapatkan data dari variabel ThreadLocal . Jika utas baru tidak memiliki data, maka kami akan menetapkannya menjadi 0. Jika ada data, kami akan menambahnya satu. Dan mari kita tulis metode utama kita:


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 bahwa variabel ThreadLocal tetap sama terlepas dari utas yang mengaksesnya, tetapi jumlah utas bertambah.

Penghitung: 1
Penghitung: 2
Penghitung: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Proses selesai dengan kode keluar 0

menghapus()

Untuk memahami cara kerja metode hapus , kami hanya akan sedikit mengubah kode di kelas ThreadDemo :


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

Dalam kode ini, jika penghitung utas adalah bilangan genap, maka kita akan memanggil metode remove() pada variabel ThreadLocal kita. Hasil:

Penghitung: 3
threadLocalCounter: 0
Penghitung: 2
threadLocalCounter: null
Penghitung: 1
threadLocalCounter: 0

Proses selesai dengan kode keluar 0

Dan di sini kita dengan mudah melihat bahwa variabel ThreadLocal di utas kedua adalah null .

ThreadLocal.withInitial()

Metode ini membuat variabel thread-local.

Implementasi 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 dapat melihat hasil dari kode kita:

Penghitung: 1
Penghitung: 2
Penghitung: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Proses selesai dengan kode keluar 0

Mengapa kita harus menggunakan variabel seperti itu?

ThreadLocal menyediakan abstraksi atas variabel lokal terkait dengan utas eksekusi java.lang.Thread .

Variabel ThreadLocal berbeda dari yang biasa karena setiap utas memiliki instance variabel yang diinisialisasi sendiri-sendiri, yang diakses melalui metode get() dan set() .

Setiap utas, yaitu turunan dari kelas Utas , memiliki peta variabel ThreadLocal yang terkait dengannya. Kunci peta adalah referensi ke objek ThreadLocal , dan nilainya adalah referensi ke variabel ThreadLocal yang "diperoleh" .

Mengapa kelas Acak tidak cocok untuk menghasilkan angka acak dalam aplikasi multithreaded?

Kami menggunakan kelas Random untuk mendapatkan angka acak. Tetapi apakah itu berfungsi dengan baik di lingkungan multithreaded? Sebenarnya tidak. Acak tidak cocok untuk lingkungan multithread, karena ketika banyak utas mengakses kelas pada saat yang sama, kinerja akan menurun.

Untuk mengatasi masalah ini, JDK 7 memperkenalkan kelas java.util.concurrent.ThreadLocalRandom untuk menghasilkan angka acak di lingkungan multithreaded. Ini terdiri dari dua kelas: ThreadLocal dan Random .

Nomor acak yang diterima oleh satu utas tidak bergantung pada utas lainnya, tetapi java.util.Random menyediakan nomor acak global. Selain itu, tidak seperti Random , ThreadLocalRandom tidak mendukung penyemaian eksplisit. Sebagai gantinya, metode ini menimpa metode setSeed() yang diwarisi dari Random , sehingga selalu melontarkan UnsupportedOperationException saat dipanggil.

Mari kita lihat metode kelas ThreadLocalRandom :

metode Tindakan
ThreadLocalRandom saat ini() Mengembalikan ThreadLocalRandom dari utas saat ini.
int selanjutnya(int bit) Menghasilkan nomor acak semu berikutnya.
double nextDouble(paling sedikit, terikat ganda) Mengembalikan nomor pseudorandom dari distribusi seragam antara paling sedikit (inklusif) dan terikat (eksklusif).
int nextInt(mint setidaknya, int terikat) Mengembalikan nomor pseudorandom dari distribusi seragam antara paling sedikit (inklusif) dan terikat (eksklusif).
panjang berikutnyaPanjang (panjang n) Mengembalikan nomor pseudorandom dari distribusi seragam antara 0 (inklusif) dan nilai yang ditentukan (eksklusif).
panjang berikutnyaPanjang(paling tidak, terikat panjang) Mengembalikan nomor pseudorandom dari distribusi seragam antara paling sedikit (inklusif) dan terikat (eksklusif).
void setSeed (biji panjang) Melempar UnsupportedOperationException . Generator ini tidak mendukung pembibitan.

Mendapatkan nomor acak menggunakan ThreadLocalRandom.current()

ThreadLocalRandom adalah kombinasi dari kelas ThreadLocal dan Random . Ini mencapai kinerja yang lebih baik dalam lingkungan multithread hanya dengan menghindari akses bersamaan ke instance kelas Random .

Mari terapkan contoh yang melibatkan banyak utas dan lihat aplikasi kita melakukannya 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 dari program kami:

Waktu yang dibutuhkan: 1
Utas 17 dihasilkan 13
Utas 18 dihasilkan 41
Utas 16 dihasilkan 99
Utas 19 dihasilkan 25
Utas 23 dihasilkan 33
Utas 24 dihasilkan 21
Utas 15 dihasilkan 15
Utas 21 dihasilkan 28
Utas 22 dihasilkan 97
Utas 20 dihasilkan 33

Dan sekarang mari kita ubah kelas RandomNumbers kita dan gunakan Random di dalamnya:


int result = new Random().nextInt(bound);
Waktu yang dibutuhkan: 5
Utas 20 dihasilkan 48
Utas 19 dihasilkan 57
Utas 18 dihasilkan 90
Utas 22 dihasilkan 43
Utas 24 dihasilkan 7
Utas 23 dihasilkan 63
Utas 15 dihasilkan 2
Utas 16 dihasilkan 40
Utas 17 dihasilkan 29
Utas 21 dihasilkan 12

Perhatikan! Dalam pengujian kami, terkadang hasilnya sama dan terkadang berbeda. Tetapi jika kita menggunakan lebih banyak utas (katakanlah 100), hasilnya akan terlihat seperti ini:

Acak — 19-25 ms
ThreadLocalRandom — 17-19 ms

Dengan demikian, semakin banyak utas dalam aplikasi kita, semakin besar kinerja yang dicapai saat menggunakan kelas Random di lingkungan multithreaded.

Untuk meringkas dan mengulangi perbedaan antara kelas Random dan ThreadLocalRandom :

Acak ThreadLocalRandom
Jika utas yang berbeda menggunakan instance Random yang sama , akan ada konflik dan kinerja akan menurun. Tidak ada konflik atau masalah, karena nomor acak yang dihasilkan bersifat lokal untuk utas saat ini.
Menggunakan rumus kongruensi linier untuk mengubah nilai awal. Generator angka acak diinisialisasi menggunakan seed yang dihasilkan secara internal.
Berguna dalam aplikasi di mana setiap utas menggunakan kumpulan objek Acaknya sendiri . Berguna dalam aplikasi di mana banyak utas menggunakan nomor acak secara paralel di kumpulan utas.
Ini adalah kelas induk. Ini adalah kelas anak-anak.