Trong bài học này, chúng ta sẽ nói chung về cách làm việc với lớp java.lang.ThreadLocal<> và cách sử dụng nó trong môi trường đa luồng.

Lớp ThreadLocal được sử dụng để lưu trữ các biến. Một tính năng đặc biệt của lớp này là nó giữ một bản sao độc lập riêng biệt của một giá trị cho mỗi luồng sử dụng nó.

Tìm hiểu sâu hơn về hoạt động của class, chúng ta có thể hình dung ra một Map ánh xạ luồng tới giá trị, từ đó luồng hiện tại lấy giá trị phù hợp khi cần sử dụng.

Hàm tạo lớp ThreadLocal

Người xây dựng Hoạt động
ThreadLocal() Tạo một biến rỗng trong Java

phương pháp

Phương pháp Hoạt động
lấy() Trả về giá trị của biến cục bộ của luồng hiện tại
bộ() Đặt giá trị của biến cục bộ cho luồng hiện tại
di dời() Xóa giá trị của biến cục bộ của chuỗi hiện tại
ThreadLocal.withInitial() Phương thức xuất xưởng bổ sung đặt giá trị ban đầu

được thiết lập()

Hãy viết một ví dụ trong đó chúng ta tạo hai bộ đếm. Đầu tiên, một biến thông thường, sẽ dùng để đếm số luồng. Thứ hai, chúng tôi sẽ bọc trong một ThreadLocal . Và chúng ta sẽ xem cách họ làm việc cùng nhau. Trước tiên, hãy viết một lớp ThreadDemo kế thừa Runnable và chứa dữ liệu của chúng ta cũng như phương thức run() cực kỳ quan trọng . Chúng tôi cũng sẽ thêm một phương thức để hiển thị bộ đếm trên màn hình:


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

Với mỗi lần chạy của lớp chúng tôi, chúng tôi tăngquầy tính tiềnbiến gọi phương thức get() để lấy dữ liệu từ biến ThreadLocal . Nếu luồng mới không có dữ liệu, thì chúng tôi sẽ đặt nó thành 0. Nếu có dữ liệu, chúng tôi sẽ tăng nó lên một. Và hãy viết phương thức chính của chúng ta :


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

}

Chạy lớp của chúng tôi, chúng tôi thấy rằng biến ThreadLocal vẫn giữ nguyên bất kể luồng nào truy cập nó, nhưng số lượng luồng tăng lên.

Bộ đếm: 1
Bộ đếm: 2
Bộ đếm: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

Quá trình kết thúc với mã thoát 0

di dời()

Để hiểu cách hoạt động của phương thức xóa , chúng ta sẽ chỉ thay đổi một chút mã trong lớp ThreadDemo :


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

Trong mã này, nếu bộ đếm luồng là một số chẵn, thì chúng ta sẽ gọi phương thức remove() trên biến ThreadLocal của chúng ta. Kết quả:

Bộ đếm: 3
threadLocalCounter: 0
Bộ đếm: 2
threadLocalCounter: null
Bộ đếm: 1
threadLocalCounter: 0

Quá trình kết thúc với mã thoát 0

Và ở đây chúng ta dễ dàng nhận thấy biến ThreadLocal ở thread thứ 2 là null .

ThreadLocal.withInitial()

Phương pháp này tạo ra một biến thread-local.

Triển khai lớp 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());
    }
}

Và chúng ta có thể xem kết quả của mã của chúng ta:

Bộ đếm: 1
Bộ đếm: 2
Bộ đếm: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

Quá trình kết thúc với mã thoát 0

Tại sao chúng ta nên sử dụng các biến như vậy?

ThreadLocal cung cấp một sự trừu tượng hóa các biến cục bộ liên quan đến luồng thực thi java.lang.Thread .

Các biến ThreadLocal khác với các biến thông thường ở chỗ mỗi luồng có phiên bản riêng, được khởi tạo riêng của biến, được truy cập thông qua các phương thức get() set() .

Mỗi luồng, tức là thể hiện của lớp Chủ đề , có một bản đồ các biến ThreadLocal được liên kết với nó. Các khóa của bản đồ là các tham chiếu đến các đối tượng ThreadLocal và các giá trị là các tham chiếu đến các biến ThreadLocal "đã nhận" .

Tại sao lớp Ngẫu nhiên không phù hợp để tạo số ngẫu nhiên trong các ứng dụng đa luồng?

Chúng tôi sử dụng lớp Random để lấy số ngẫu nhiên. Nhưng nó có hoạt động tốt trong môi trường đa luồng không? Trên thực tế, không. Random không phù hợp với môi trường đa luồng, bởi vì khi nhiều luồng truy cập vào một lớp cùng một lúc, hiệu suất sẽ bị ảnh hưởng.

Để giải quyết vấn đề này, JDK 7 đã giới thiệu lớp java.util.concurrent.ThreadLocalRandom để tạo các số ngẫu nhiên trong môi trường đa luồng. Nó bao gồm hai lớp: ThreadLocalRandom .

Các số ngẫu nhiên mà một luồng nhận được độc lập với các luồng khác, nhưng java.util.Random cung cấp các số ngẫu nhiên trên toàn cầu. Ngoài ra, không giống như Random , ThreadLocalRandom không hỗ trợ tạo hạt rõ ràng. Thay vào đó, nó sẽ ghi đè phương thức setSeed() được kế thừa từ Random để nó luôn ném ra một ngoại lệ UnsupportedOperationException khi được gọi.

Hãy xem các phương thức của lớp ThreadLocalRandom :

Phương pháp Hoạt động
ThreadLocalRandom hiện tại() Trả về ThreadLocalRandom của chuỗi hiện tại.
int tiếp theo(int bit) Tạo số giả ngẫu nhiên tiếp theo.
double nextDouble(ít nhất gấp đôi, bị ràng buộc gấp đôi) Trả về một số giả ngẫu nhiên từ một phân phối đồng nhất giữa ít nhất (bao gồm) và bị ràng buộc (độc quyền).
int nextInt(int ít nhất, int bị ràng buộc) Trả về một số giả ngẫu nhiên từ một phân phối đồng nhất giữa ít nhất (bao gồm) và bị ràng buộc (độc quyền).
dài nextLong(dài n) Trả về một số giả ngẫu nhiên từ phân phối đồng đều giữa 0 (bao gồm) và giá trị đã chỉ định (không bao gồm).
long nextLong(dài ít nhất, giới hạn dài) Trả về một số giả ngẫu nhiên từ một phân phối đồng nhất giữa ít nhất (bao gồm) và bị ràng buộc (độc quyền).
void setSeed(hạt dài) Ném UnsupportedOperationException . Máy phát điện này không hỗ trợ gieo hạt.

Nhận số ngẫu nhiên bằng ThreadLocalRandom.current()

ThreadLocalRandom là sự kết hợp của các lớp ThreadLocal Random . Nó đạt được hiệu suất tốt hơn trong môi trường đa luồng bằng cách đơn giản tránh mọi truy cập đồng thời vào các phiên bản của lớp Ngẫu nhiên .

Hãy triển khai một ví dụ liên quan đến nhiều luồng và xem ứng dụng của chúng ta thực hiện với lớp 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));
    }
}

Kết quả của chương trình của chúng tôi:

Thời gian thực hiện: 1
Chủ đề 17 được tạo 13
Chủ đề 18 được tạo 41
Chủ đề 16 được tạo 99
Chủ đề 19 được tạo 25
Chủ đề 23 được tạo 33
Chủ đề 24 được tạo 21
Chủ đề 15 được tạo 15
Chủ đề 21 được tạo 28
Chủ đề 22 được tạo 97
Chủ đề 20 được tạo 33

Và bây giờ, hãy thay đổi lớp RandomNumbers của chúng ta và sử dụng Random trong đó:


int result = new Random().nextInt(bound);
Thời gian thực hiện: 5
Chủ đề 20 được tạo 48
Chủ đề 19 được tạo 57
Chủ đề 18 được tạo 90
Chủ đề 22 được tạo 43
Chủ đề 24 được tạo 7
Chủ đề 23 được tạo 63
Chủ đề 15 được tạo 2
Chủ đề 16 được tạo 40
Chủ đề 17 được tạo 29
Chủ đề 21 được tạo 12

Hãy lưu ý! Trong các thử nghiệm của chúng tôi, đôi khi kết quả giống nhau và đôi khi chúng khác nhau. Nhưng nếu chúng ta sử dụng nhiều chủ đề hơn (ví dụ: 100), kết quả sẽ như thế này:

Ngẫu nhiên — 19-25 mili giây
ThreadLocalRandom — 17-19 mili giây

Theo đó, càng nhiều luồng trong ứng dụng của chúng ta, hiệu suất đạt được khi sử dụng lớp Ngẫu nhiên trong môi trường đa luồng càng lớn.

Để tổng hợp và nhắc lại sự khác biệt giữa các lớp RandomThreadLocalRandom :

Ngẫu nhiên Chủ đềLocalRandom
Nếu các luồng khác nhau sử dụng cùng một phiên bản Random thì sẽ xảy ra xung đột và hiệu suất sẽ bị ảnh hưởng. Không có xung đột hoặc sự cố, vì các số ngẫu nhiên được tạo là cục bộ của luồng hiện tại.
Sử dụng công thức đồng dạng tuyến tính để thay đổi giá trị ban đầu. Trình tạo số ngẫu nhiên được khởi tạo bằng cách sử dụng hạt giống được tạo bên trong.
Hữu ích trong các ứng dụng mà mỗi luồng sử dụng tập hợp các đối tượng Ngẫu nhiên của riêng nó . Hữu ích trong các ứng dụng có nhiều luồng sử dụng song song các số ngẫu nhiên trong nhóm luồng.
Đây là một lớp cha. Đây là lớp con.