Bu derste, genel olarak java.lang.ThreadLocal<> sınıfıyla çalışmaktan ve onu çok iş parçacıklı bir ortamda nasıl kullanacağımızdan bahsedeceğiz .

ThreadLocal sınıfı , değişkenleri depolamak için kullanılır. Bu sınıfın ayırt edici bir özelliği, kendisini kullanan her iş parçacığı için bir değerin ayrı ve bağımsız bir kopyasını tutmasıdır.

Sınıfın işleyişini daha derinlemesine incelersek, dizileri, kullanması gerektiğinde uygun değeri aldığı mevcut iş parçacığının değerlerle eşlediği bir Harita hayal edebiliriz.

ThreadLocal sınıf yapıcısı

Yapıcı Aksiyon
ThreadLocal() Java'da boş bir değişken oluşturur

Yöntemler

Yöntem Aksiyon
elde etmek() Geçerli iş parçacığının yerel değişkeninin değerini döndürür
ayarlamak() Geçerli iş parçacığı için yerel değişkenin değerini ayarlar
kaldırmak() Geçerli iş parçacığının yerel değişkeninin değerini kaldırır
ThreadLocal.withInitial() İlk değeri ayarlayan ek fabrika yöntemi

hazırlan()

İki sayaç oluşturduğumuz bir örnek yazalım. Sıradan bir değişken olan ilki, iş parçacığı sayısını saymak için olacaktır. İkincisi, bir ThreadLocal içine saracağız . Ve birlikte nasıl çalıştıklarını göreceğiz. Öncelikle, Runnable'ı devralan ve verilerimizi ve çok önemli run() yöntemini içeren bir ThreadDemo sınıfı yazalım . Sayaçları ekranda görüntülemek için bir yöntem de ekleyeceğiz:


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

Sınıfımızın her çalışmasında,tezgahdeğişkeni, ThreadLocal değişkeninden veri almak için get() yöntemini çağırır . Yeni iş parçacığında veri yoksa, onu 0 olarak ayarlayacağız. Veri varsa, onu bir artıracağız. Ve ana yöntemimizi yazalım :


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

}

Sınıfımızı çalıştırırken, ThreadLocal değişkeninin ona erişen thread ne olursa olsun aynı kaldığını fakat thread sayısının arttığını görüyoruz.

Sayaç: 1
Sayaç: 2
Sayaç: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Çıkış kodu 0 ile işlem tamamlandı

kaldırmak()

Remove yönteminin nasıl çalıştığını anlamak için ThreadDemo sınıfındaki kodu biraz değiştireceğiz :


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

Bu kodda, thread sayacı çift sayı ise, ThreadLocal değişkenimiz üzerinde remove() metodunu çağıracağız . Sonuç:

Sayaç: 3
threadLocalCounter: 0
Sayaç: 2
threadLocalCounter: null
Sayaç: 1
threadLocalCounter: 0

Çıkış kodu 0 ile işlem tamamlandı

Ve burada kolayca ikinci iş parçacığındaki ThreadLocal değişkeninin null olduğunu görüyoruz .

ThreadLocal.withInitial()

Bu yöntem, yerel bir iş parçacığı değişkeni oluşturur.

ThreadDemo sınıfının uygulanması :


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

Ve kodumuzun sonucuna bakabiliriz:

Sayaç: 1
Sayaç: 2
Sayaç: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

İşlem, çıkış kodu 0 ile tamamlandı

Neden bu tür değişkenleri kullanmalıyız?

ThreadLocal, java.lang.Thread yürütme iş parçacığına ilişkin olarak yerel değişkenler üzerinde bir soyutlama sağlar.

ThreadLocal değişkenleri, her iş parçacığının , get() ve set() yöntemleriaracılığıyla erişilen kendi, bireysel olarak başlatılmış değişken örneğine sahip olması bakımından sıradan değişkenlerden farklıdır

Her iş parçacığı, yani Thread sınıfı örneği , kendisiyle ilişkili ThreadLocal değişkenlerinin bir haritasına sahiptir . Haritanın anahtarları, ThreadLocal nesnelerine referanslardır ve değerler, "edinilmiş" ThreadLocal değişkenlerine referanslardır .

Random sınıfı, çok iş parçacıklı uygulamalarda rasgele sayılar üretmek için neden uygun değil?

Rastgele sayılar elde etmek için Random sınıfını kullanırız . Ancak çok iş parçacıklı bir ortamda da aynı şekilde çalışıyor mu? Aslında hayır. Rastgele, çok iş parçacıklı ortamlar için uygun değildir, çünkü birden çok iş parçacığı bir sınıfa aynı anda eriştiğinde performans düşer.

Bu sorunu çözmek için JDK 7, çok iş parçacıklı bir ortamda rasgele sayılar üretmek için java.util.concurrent.ThreadLocalRandom sınıfını tanıttı. İki sınıftan oluşur: ThreadLocal ve Random .

Bir iş parçacığı tarafından alınan rasgele sayılar diğer iş parçacıklarından bağımsızdır, ancak java.util.Random genel olarak rasgele sayılar sağlar. Ayrıca, Random'dan farklı olarak , ThreadLocalRandom açık tohumlamayı desteklemez. Bunun yerine, Random öğesinden devralınan setSeed() yöntemini geçersiz kılar , böylece çağrıldığında her zaman bir UnsupportedOperationException atar.

Şimdi ThreadLocalRandom sınıfının metotlarına bakalım :

Yöntem Aksiyon
ThreadLocalRandom akımı() Geçerli iş parçacığının ThreadLocalRandom değerini döndürür.
int sonraki(int bit) Bir sonraki sözde rasgele sayıyı üretir.
double nextDouble(en az çift, çift sınır) En küçük (dahil) ve sınır (özel) arasındaki tekdüze dağılımdan sözde rasgele bir sayı döndürür .
int nextInt(en az int, int bağlı) En küçük (dahil) ve sınır (özel) arasındaki tekdüze dağılımdan sözde rasgele bir sayı döndürür.
uzun sonrakiUzun(uzun n) 0 (dahil) ile belirtilen değer (hariç) arasındaki tekdüze dağılımdan sözde rasgele bir sayı döndürür.
long nextLong(en uzun, en uzun bağlı) En küçük (dahil) ve sınır (özel) arasındaki tekdüze dağılımdan sözde rasgele bir sayı döndürür.
geçersiz setSeed(uzun tohum) UnsupportedOperationException atar . Bu jeneratör tohumlamayı desteklemiyor.

ThreadLocalRandom.current() kullanarak rasgele sayılar alma

ThreadLocalRandom, ThreadLocal ve Random sınıflarının birleşimidir. Random sınıfının örneklerine herhangi bir eşzamanlı erişimden kaçınarak çok iş parçacıklı bir ortamda daha iyi performans elde eder.

Birden fazla iş parçacığı içeren bir örnek uygulayalım ve uygulamamızın ThreadLocalRandom sınıfıyla ne yaptığını görelim:


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

Programımızın sonucu:

Alınan süre: 1
Konu 17 oluşturuldu 13
Konu 18 oluşturuldu 41
Konu 16 oluşturuldu 99
Konu 19 oluşturuldu 25
Konu 23 oluşturuldu 33
Konu 24 oluşturuldu 21
Konu 15 oluşturuldu 15 Konu
21 oluşturuldu 28
Konu 22 oluşturuldu 97
Konu 20 oluşturuldu 33

Şimdi RandomNumbers sınıfımızı değiştirelim ve içinde Random kullanalım:


int result = new Random().nextInt(bound);
Alınan süre: 5
Konu 20 oluşturuldu 48
Konu 19 oluşturuldu 57
Konu 18 oluşturuldu 90
Konu 22 oluşturuldu 43
Konu 24 oluşturuldu 7
Konu 23 oluşturuldu 63
Konu 15 oluşturuldu 2
Konu 16 oluşturuldu 40
Konu 17 oluşturuldu 29
Konu 21 oluşturuldu 12

Not al! Yaptığımız testlerde bazen sonuçlar aynı bazen de farklı çıktı. Ancak daha fazla iş parçacığı kullanırsak (diyelim ki 100), sonuç şöyle görünecektir:

Rastgele — 19-25 ms
ThreadLocalRandom — 17-19 ms

Buna göre, uygulamamızda ne kadar çok iş parçacığı varsa, çok iş parçacıklı bir ortamda Random sınıfını kullanırken elde edilen performans o kadar yüksek olur.

Random ve ThreadLocalRandom sınıfları arasındaki farkları özetlemek ve yinelemek için :

Rastgele ThreadLocalRandom
Farklı iş parçacıkları aynı Random örneğini kullanırsa , çakışmalar olur ve performans düşer. Oluşturulan rasgele sayılar mevcut iş parçacığında yerel olduğundan herhangi bir çakışma veya sorun yoktur.
İlk değeri değiştirmek için doğrusal bir uyumlu formül kullanır. Rastgele sayı üreteci, dahili olarak üretilmiş bir tohum kullanılarak başlatılır.
Her iş parçacığının kendi Rastgele nesne kümesini kullandığı uygulamalarda kullanışlıdır . Birden çok iş parçacığının iş parçacığı havuzlarında paralel olarak rasgele sayılar kullandığı uygulamalarda kullanışlıdır.
Bu bir ebeveyn sınıfıdır. Bu bir çocuk sınıfıdır.