在本課中,我們將大體討論使用java.lang.ThreadLocal<>類以及如何在多線程環境中使用它。

ThreadLocal類用於存儲變量這個類的一個顯著特徵是它為每個使用它的線程保留一個單獨的獨立值副本。

深入研究類的操作,我們可以想像一個映射,將線程映射到值,當前線程需要使用時從中取合適的值。

ThreadLocal類構造器

構造器 行動
線程本地() 在 Java 中創建一個空變量

方法

方法 行動
得到() 返回當前線程局部變量的值
放() 為當前線程設置局部變量的值
消除() 移除當前線程局部變量的值
ThreadLocal.withInitial() 設置初始值的附加工廠方法

獲取()和設置()

讓我們寫一個例子,我們創建兩個計數器。第一個是普通變量,用於計算線程數。第二個我們將包裝在ThreadLocal中。我們將看到它們如何協同工作。首先,讓我們編寫一個繼承Runnable並包含我們的數據和最重要的run()方法的ThreadDemo類。我們還將添加一個在屏幕上顯示計數器的方法:


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

每次上課,我們都會增加櫃檯變量調用get()方法從ThreadLocal變量中獲取數據。如果新線程沒有數據,那麼我們就把它置0。如果有數據,我們就加1。讓我們編寫我們的主要方法:


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

}

運行我們的類,我們看到無論訪問它的線程如何, ThreadLocal變量都保持不變,但線程數增加了。

計數器:1
計數器:2
計數器:3
threadLocalCounter:0
threadLocalCounter:0
threadLocalCounter:0

進程已完成,退出代碼為 0

消除()

要了解remove方法的工作原理,我們只需稍微更改ThreadDemo類中的代碼:


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

在這段代碼中,如果線程計數器是偶數,那麼我們將對ThreadLocal變量調用remove()方法。結果:

計數器:3
threadLocalCounter:0
計數器:2
threadLocalCounter:null
計數器:1
threadLocalCounter:0

進程已完成,退出代碼為 0

在這裡我們很容易看到第二個線程中的ThreadLocal變量為null

ThreadLocal.withInitial()

此方法創建一個線程局部變量。

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

我們可以看看代碼的結果:

計數器:1
計數器:2
計數器:3
threadLocalCounter:1
threadLocalCounter:1
threadLocalCounter:1

進程已完成,退出代碼為 0

我們為什麼要使用這樣的變量?

ThreadLocal提供了與執行線程java.lang.Thread相關的局部變量的抽象。

ThreadLocal變量與普通變量的不同之處在於,每個線程都有自己的、單獨初始化的變量實例,可以通過 get ()set()方法訪問。

每個線程,即Thread類的實例,都有一個與之關聯的ThreadLocal變量映射。映射的鍵是對ThreadLocal對象的引用,值是對“獲取的” ThreadLocal變量的引用。

為什麼Random類不適合在多線程應用中生成隨機數?

我們使用Random類來獲取隨機數。但它在多線程環境中是否同樣有效?實際上,不。Random不適合多線程環境,因為當多個線程同時訪問一個類時,性能會受到影響。

為了解決這個問題,JDK 7 引入了java.util.concurrent.ThreadLocalRandom類來在多線程環境中生成隨機數。它由兩個類組成:ThreadLocalRandom

一個線程接收到的隨機數是獨立於其他線程的,而java.util.Random提供的是全局隨機數。此外,與Random不同,ThreadLocalRandom不支持顯式播種。相反,它覆蓋了從Random繼承的setSeed()方法,因此它在調用時總是拋出UnsupportedOperationException 。

我們看一下ThreadLocalRandom類的方法:

方法 行動
ThreadLocalRandom current() 返回當前線程的 ThreadLocalRandom。
下一個(整數位) 生成下一個偽隨機數。
double nextDouble(double least, double bound) 從最小(包含)和綁定(排除)之間的均勻分佈返回偽隨機數。
int nextInt(int least, int bound) 從最小(包含)和綁定(排除)之間的均勻分佈返回偽隨機數。
長 nextLong(long n) 從 0(含)和指定值(不含)之間的均勻分佈返回一個偽隨機數。
long nextLong(最短,長界) 從最小(包含)和綁定(排除)之間的均勻分佈返回偽隨機數。
void setSeed(長種子) 拋出UnsupportedOperationException。此生成器不支持播種。

使用 ThreadLocalRandom.current() 獲取隨機數

ThreadLocalRandom是ThreadLocalRandom類的組合。它通過簡單地避免對Random類的實例的任何並發訪問,在多線程環境中實現了更好的性能。

讓我們實現一個涉及多線程的示例,看看我們的應用程序如何處理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));
    }
}

我們程序的結果:

所用時間: 1
線程 17 生成 13
線程 18 生成 41
線程 16 生成 99
線程 19 生成 25
線程 23 生成 33
線程 24 生成 21
線程 15 生成 15
線程 21 生成 28
線程 22 生成 97
線程 20 生成 33

現在讓我們更改我們的RandomNumbers類並在其中使用Random :


int result = new Random().nextInt(bound);
所用時間:5
線程 20 生成 48
線程 19 生成 57
線程 18 生成 90
線程 22 生成 43
線程 24 生成 7
線程 23 生成 63線程 15
生成 2
線程 16 生成 40
線程 17 生成 29
線程 21 生成 12

做記錄!在我們的測試中,結果有時相同,有時不同。但是如果我們使用更多線程(比如 100 個),結果將如下所示:

隨機 — 19-25 毫秒
ThreadLocalRandom — 17-19 毫秒

因此,我們的應用程序中的線程越多,在多線程環境中使用Random類時性能受到的影響就越大。

總結並重申RandomThreadLocalRandom類之間的區別:

隨機的 線程本地隨機
如果不同的線程使用同一個Random實例,就會發生衝突並且性能會受到影響。 沒有衝突或問題,因為生成的隨機數是當前線程本地的。
使用線性同餘公式更改初始值。 隨機數生成器使用內部生成的種子進行初始化。
在每個線程都使用自己的一組Random對象的應用程序中很有用。 在多個線程在線程池中並行使用隨機數的應用程序中很有用。
這是一個父類。 這是一個兒童班。