Tại sao các biến Java ThreadLocal phải tĩnh


101

Tôi đã đọc JavaDoc cho Threadlocal tại đây

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

và nó cho biết "Các cá thể ThreadLocal thường là các trường tĩnh riêng tư trong các lớp muốn liên kết trạng thái với một luồng (ví dụ: ID người dùng hoặc ID giao dịch)."

Nhưng câu hỏi của tôi là tại sao họ lại chọn làm cho nó tĩnh (thông thường) - nó khiến mọi thứ hơi khó hiểu khi có trạng thái "mỗi luồng" nhưng các trường lại tĩnh?

Câu trả lời:


131

Bởi vì nếu đó là một trường cấp độ cá thể, thì nó thực sự sẽ là "Mỗi luồng - Mỗi trường hợp", chứ không chỉ là "Mỗi luồng" được đảm bảo. Đó thường không phải là ngữ nghĩa bạn đang tìm kiếm.

Thông thường, nó nắm giữ một thứ gì đó như các đối tượng nằm trong phạm vi Cuộc trò chuyện của người dùng, Yêu cầu web, v.v. Bạn không muốn chúng cũng nằm trong phạm vi phụ đối với phiên bản của lớp.
Một yêu cầu web => một phiên Kiên trì.
Không phải một yêu cầu web => một phiên liên tục cho mỗi đối tượng.


2
Tôi thích lời giải thích này vì nó cho thấy cách sử dụng ThreadLocal
kellyfj

4
Per-thread-per-instance có thể là một ngữ nghĩa hữu ích, nhưng hầu hết các cách sử dụng cho mẫu đó sẽ liên quan đến rất nhiều đối tượng nên sẽ tốt hơn nếu sử dụng a ThreadLocalđể giữ một tham chiếu đến một tập hợp băm ánh xạ các đối tượng đến các cá thể mỗi luồng.
supercat

@optional Nó chỉ có nghĩa là mỗi phiên bản của non-static ThreadLocalsẽ giữ dữ liệu cục bộ của nó ngay cả khi những ThreadLocalcá thể đó tồn tại trong cùng một chuỗi. Không nhất thiết là sai khi làm điều đó - tôi cho rằng nó chỉ có thể là mẫu ít phổ biến nhất trong hai mẫu
geg

17

Làm cho nó tĩnh hoặc nếu bạn đang cố gắng tránh bất kỳ trường tĩnh nào trong lớp của mình - hãy đặt chính lớp đó thành một lớp đơn và sau đó bạn có thể sử dụng một cách an toàn ThreadLocal mức cá thể miễn là bạn có sẵn lớp đơn đó trên toàn cầu.



3

Lý do là các biến được truy cập thông qua một con trỏ liên kết với luồng. Chúng hoạt động giống như các biến toàn cục với phạm vi luồng, do đó tĩnh là phù hợp nhất. Đây là cách mà bạn có được trạng thái cục bộ của luồng trong những thứ như pthreads, vì vậy đây có thể chỉ là một sự cố của lịch sử và quá trình triển khai.


1

Việc sử dụng cho một threadlocal trên mỗi trường hợp trên mỗi chuỗi là nếu bạn muốn một cái gì đó hiển thị trong tất cả các phương thức của một đối tượng và đảm bảo an toàn cho chuỗi mà không cần đồng bộ hóa quyền truy cập vào nó như cách bạn làm đối với một trường thông thường.


1

Tham khảo điều này , điều này cho sự hiểu biết tốt hơn.

Nói tóm lại, ThreadLocalđối tượng hoạt động giống như một bản đồ khóa-giá trị. Khi luồng gọi ThreadLocal get/setphương thức, nó sẽ truy xuất / lưu trữ đối tượng luồng trong khóa của bản đồ và giá trị trong giá trị của bản đồ. Đó là lý do tại sao các luồng khác nhau có bản sao giá trị khác nhau (mà bạn muốn lưu trữ cục bộ), bởi vì nó nằm trong mục nhập của bản đồ khác nhau.

Đó là lý do tại sao bạn chỉ cần một bản đồ để giữ tất cả các giá trị. Mặc dù không cần thiết, bạn cũng có thể có nhiều bản đồ (không cần khai báo static) để giữ từng đối tượng luồng, điều này hoàn toàn dư thừa, đó là lý do tại sao biến static được ưu tiên hơn.


-1

static final ThreadLocal các biến là an toàn chủ đề.

staticlàm cho biến ThreadLocal có sẵn trên nhiều lớp chỉ cho luồng tương ứng. đó là một loại phân tách biến Toàn cục của các biến cục bộ luồng tương ứng trên nhiều lớp.

Chúng ta có thể kiểm tra độ an toàn của luồng này với mẫu mã sau.

  • CurrentUser - lưu trữ id người dùng hiện tại trong ThreadLocal
  • TestService- Dịch vụ đơn giản với phương pháp - getUser()để tìm nạp người dùng hiện tại từ CurrentUser.
  • TestThread - lớp này được sử dụng để tạo nhiều luồng và thiết lập đồng thời các mã sử dụng

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Chạy lớp chính TestThread. Đầu ra -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Tóm tắt phân tích

  1. luồng "chính" bắt đầu và đặt người dùng hiện tại là "62", song song luồng "ForkJoinPool.commonPool-worker-2" bắt đầu và đặt người dùng hiện tại là "31", song song đó luồng "ForkJoinPool.commonPool-worker-3" bắt đầu và đặt hiện tại người dùng là "81", song song với chuỗi "ForkJoinPool.commonPool-worker-1" bắt đầu và đặt người dùng hiện tại là "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. Tất cả các chuỗi trên sẽ ngủ trong 3 giây
  3. mainthực thi kết thúc và in người dùng hiện tại là "62", ForkJoinPool.commonPool-worker-1thực thi song song kết thúc và in người dùng hiện tại là "87", ForkJoinPool.commonPool-worker-2thực thi song song kết thúc và in người dùng hiện tại là "31", ForkJoinPool.commonPool-worker-3thực thi song song kết thúc và in người dùng hiện tại là "81"

Sự suy luận

Các luồng đồng thời có thể truy xuất các mã sử dụng chính xác ngay cả khi nó đã được khai báo là "static final ThreadLocal"

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.