Khi nào và làm thế nào tôi nên sử dụng biến ThreadLocal?


877

Khi nào tôi nên sử dụng một ThreadLocalbiến?

Nó được sử dụng như thế nào?


11
Nếu bạn đang sử dụng ThreadLocal, đừng bao giờ viết một trình bao bọc trên nó !!! Mỗi và mọi nhà phát triển muốn sử dụng biến phải biết đó là 'ThreadLocal'
Không phải là lỗi

2
@Notabug Nếu bạn rõ ràng, bạn có thể đặt tên cho biến của mình để nó đang xử lý giá trị ThreadLocal, vd RequestUtils.getCurrentRequestThreadLocal(). Mặc dù không nói điều này rất thanh lịch, nhưng điều này xuất phát từ thực tế rằng bản thân ThreadLocal không thanh lịch trong hầu hết các trường hợp.
lốc

@Sasha Có thể sử dụng ThreadLocal để lưu trữ dấu vết thực thi
gaurav

Câu trả lời:


864

Một cách sử dụng có thể (và phổ biến) là khi bạn có một số đối tượng không an toàn cho luồng, nhưng bạn muốn tránh đồng bộ hóa quyền truy cập vào đối tượng đó (Tôi đang nhìn vào bạn, SimpleDateFormat ). Thay vào đó, cung cấp cho mỗi luồng đối tượng riêng của đối tượng.

Ví dụ:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Tài liệu .


160
Một cách khác để đồng bộ hóa hoặc luồng dữ liệu là biến biến thành một biến cục bộ. Vars địa phương luôn luôn chủ đề an toàn. Tôi cho rằng việc đặt DateFormats cục bộ là rất tệ vì chúng rất tốn kém để tạo, nhưng tôi chưa bao giờ thấy các số liệu vững chắc về chủ đề này.
Julien Chastang

18
Đây là giá cao để trả cho hack SimpleDateFormat. Có lẽ tốt hơn là sử dụng một sự thay thế an toàn chủ đề . Nếu bạn đồng ý rằng singletons là xấu thì ThreadLocalthậm chí còn tồi tệ hơn.
Alexander Ryzhov

4
@overthink Có bất kỳ lý do để tuyên bố ThreadLocal tĩnh và cuối cùng, ý tôi là hiệu suất hoặc một cái gì đó?
Sachin Gorade

4
Phương thức ThreadLocal.get () sẽ gọi ThreadLocal.initialValue () (một lần) cho mỗi luồng, có nghĩa là một đối tượng SimpleDateFormat được tạo cho mỗi luồng. Có phải tốt hơn không khi chỉ có SimpleDateFormat làm biến cục bộ (vì chúng ta không phải xử lý các vấn đề thu gom rác)?
sudeepdino008

3
Chỉ cần cẩn thận không sử dụng khởi tạo cú đúp cho SimpleDateFormat của bạn vì điều đó sẽ tạo ra một lớp ẩn danh để trình tải lớp của bạn không thể được thu gom rác. rò rỉ bộ nhớ: return new SimpleDateFormat () {{applicationPotype ("yyyyMMdd HHmm")}};
dùng1944408

427

Vì a ThreadLocallà tham chiếu đến dữ liệu trong một số đã cho Thread, nên bạn có thể bị rò rỉ tải lớp khi sử dụng ThreadLocals trong các máy chủ ứng dụng bằng cách sử dụng nhóm luồng. Bạn cần phải rất cẩn thận về việc làm sạch bất kỳ ThreadLocalbạnget() hoặc set()bằng ThreadLocal's remove()phương pháp.

Nếu bạn không dọn dẹp khi hoàn tất, mọi tham chiếu mà nó chứa cho các lớp được tải như một phần của ứng dụng web đã triển khai sẽ vẫn còn trong đống vĩnh viễn và sẽ không bao giờ được thu gom rác. Việc triển khai lại / không triển khai ứng dụng web sẽ không dọn sạch từng Threadtham chiếu đến (các) lớp ứng dụng web của bạn vì Threadđây không phải là thứ thuộc sở hữu của ứng dụng web của bạn. Mỗi lần triển khai liên tiếp sẽ tạo ra một thể hiện mới của lớp sẽ không bao giờ được thu gom rác.

Bạn sẽ kết thúc với ngoại lệ bộ nhớ do java.lang.OutOfMemoryError: PermGen spacevà sau khi một số googling có thể sẽ chỉ tăng -XX:MaxPermSizethay vì sửa lỗi.

Nếu cuối cùng bạn gặp phải những vấn đề này, bạn có thể xác định luồng và lớp nào đang giữ lại các tham chiếu này bằng cách sử dụng Trình phân tích bộ nhớ của Eclipse và / hoặc bằng cách làm theo hướng dẫntheo dõi của Frank Kieviet .

Cập nhật: Phát hiện lại mục blog của Alex Vasseur giúp tôi theo dõi một số ThreadLocalvấn đề tôi đang gặp phải.


11
Alex Vasseur chuyển blog của mình. Đây là một liên kết hiện tại đến bài viết rò rỉ bộ nhớ.
Kenster

12
Có vẻ như chủ đề mà Julien liên kết đã chuyển đến đâyrất đáng để đọc
Donal Fellows

28
Đây là rất nhiều sự ủng hộ cho một câu trả lời rằng, trong khi thông tin, không có cách nào thực sự trả lời câu hỏi.
Robin

8
Bây giờ PermGen bị Java 8 giết, điều này có thay đổi câu trả lời này theo bất kỳ cách nào không?
Máy giặt Evil

13
@Robin: Tôi không đồng ý. Câu hỏi là về cách sử dụng ThreadLocal một cách chính xác, để hiểu đầy đủ về bất kỳ khái niệm nào (ThreadLocal ở đây), điều quan trọng là phải hiểu cách không sử dụng nó và những rủi ro khi sử dụng nó một cách bất cẩn, và đó là câu trả lời của Phil. Tôi rất vui vì anh ấy đã đề cập đến điểm không được nêu trong bất kỳ câu trả lời nào khác. Tất cả những phiếu bầu đó đều xứng đáng. Tôi tin rằng SO nên xây dựng sự hiểu biết về các khái niệm thay vì chỉ là một trang web QA.
Saurabh Patil

166

Nhiều khung công tác sử dụng ThreadLocals để duy trì một số ngữ cảnh liên quan đến luồng hiện tại. Ví dụ: khi giao dịch hiện tại được lưu trữ trong ThreadLocal, bạn không cần chuyển nó dưới dạng tham số thông qua mọi lệnh gọi phương thức, trong trường hợp ai đó xuống ngăn xếp cần truy cập vào nó. Các ứng dụng web có thể lưu trữ thông tin về yêu cầu và phiên hiện tại trong ThreadLocal, để ứng dụng có quyền truy cập dễ dàng vào chúng. Với Guice, bạn có thể sử dụng ThreadLocals khi triển khai phạm vi tùy chỉnh cho các đối tượng được chèn ( phạm vi servlet mặc định của Guice có thể cũng sử dụng chúng).

ThreadLocals là một loại biến toàn cục (mặc dù ít tệ hơn vì chúng bị hạn chế ở một luồng), vì vậy bạn nên cẩn thận khi sử dụng chúng để tránh tác dụng phụ không mong muốn và rò rỉ bộ nhớ. Thiết kế API của bạn để các giá trị ThreadLocal sẽ luôn tự động bị xóa khi không cần thiết nữa và việc sử dụng API không chính xác sẽ không thể thực hiện được (ví dụ như thế này ). ThreadLocals có thể được sử dụng để làm cho mã sạch hơn và trong một số trường hợp hiếm hoi, chúng là cách duy nhất để làm cho một cái gì đó hoạt động (dự án hiện tại của tôi có hai trường hợp như vậy; chúng được ghi lại ở đây trong "Biến trường tĩnh và biến toàn cầu").


4
Đó chính xác là cách khung exPOJO (www.expojo.com) cho phép truy cập vào ORM Phiên / PersistenceManager mà không cần chi phí chú thích và tiêm. Nó giống như 'tiêm luồng' thay vì 'tiêm đối tượng'. Nó cung cấp quyền truy cập vào các phụ thuộc mà không có yêu cầu (và chi phí chung) về việc chúng được nhúng vào mọi đối tượng có thể cần các phụ thuộc đó. Thật đáng kinh ngạc khi 'trọng lượng nhẹ' bạn có thể tạo khung DI khi bạn sử dụng phương pháp tiêm ren thay vì DI cổ điển (ví dụ: Mùa xuân, v.v.)
Volksman

27
Tại sao tôi phải cuộn xuống để tìm câu trả lời thiết yếu này!?
Jeremy Stein

1
@Esko, Trong dự án của bạn, việc bạn sử dụng ThreadLocal trong mã băm và bằng là không cần thiết. Cách tiếp cận tốt hơn là tạo hoặc chuyển tham chiếu đến hàm băm / bằng bất cứ khi nào cần thiết. Bằng cách này, bạn hoàn toàn có thể tránh được sự cần thiết của hack ThreadLocal.
Pacerier


1
Đây là lời giải thích tốt nhất về việc sử dụng TL.
Dexter

52

Trong Java, nếu bạn có một mốc thời gian có thể thay đổi theo từng luồng, các lựa chọn của bạn là truyền dữ liệu đó cho mọi phương thức cần (hoặc có thể cần) hoặc liên kết mốc đó với luồng. Truyền dữ liệu xung quanh mọi nơi có thể khả thi nếu tất cả các phương thức của bạn đã cần chuyển qua một biến "bối cảnh" chung.

Nếu đó không phải là trường hợp, bạn có thể không muốn làm lộn xộn chữ ký phương thức của mình với một tham số bổ sung. Trong một thế giới không có luồng, bạn có thể giải quyết vấn đề với tương đương Java của một biến toàn cục. Trong một từ có luồng, tương đương với biến toàn cục là biến cục bộ-luồng.


13
Vì vậy, bạn nên tránh chủ đề địa phương giống như cách bạn tránh toàn cầu. Tôi không thể chấp nhận rằng việc tạo ra các quả cầu (luồng cục bộ) thay vì truyền các giá trị xung quanh là điều tốt, mọi người không thích vì nó thường tiết lộ các vấn đề kiến ​​trúc mà họ không muốn khắc phục.
Juan Mendes

10
Có lẽ ... Tuy nhiên, nó có thể hữu ích nếu bạn có một cơ sở mã lớn, hiện có mà bạn phải thêm một mốc dữ liệu mới phải được truyền đi khắp nơi , ví dụ như bối cảnh phiên, giao dịch db, người dùng đăng nhập, v.v. .
Cornel Masson

6
Kudos để sử dụng mốc thay vì dữ liệu.
sâu

Điều này làm tôi tò mò. 'datum' is the singular form and 'data' is the plural form.
Siddhartha

23

Có một ví dụ rất hay trong cuốn sách Java đồng thời trong thực tiễn . Nơi tác giả ( Joshua Bloch ) giải thích cách giam cầm Thread là một trong những cách đơn giản nhất để đạt được sự an toàn của luồng và ThreadLocal là phương thức chính thức hơn để duy trì việc giam cầm luồng. Cuối cùng, ông cũng giải thích cách mọi người có thể lạm dụng nó bằng cách sử dụng nó như các biến toàn cầu.

Tôi đã sao chép văn bản từ cuốn sách được đề cập nhưng mã 3.10 bị thiếu vì nó không quan trọng lắm để hiểu nên sử dụng ThreadLocal ở đâu.

Các biến chủ đề cục bộ thường được sử dụng để ngăn chia sẻ trong các thiết kế dựa trên các biến Singletons hoặc biến toàn cục. Ví dụ, một ứng dụng đơn luồng có thể duy trì kết nối cơ sở dữ liệu toàn cầu được khởi tạo khi khởi động để tránh phải truyền Kết nối cho mọi phương thức. Do các kết nối JDBC có thể không an toàn cho luồng, nên một ứng dụng đa luồng sử dụng kết nối toàn cầu mà không có sự phối hợp bổ sung cũng không an toàn cho luồng. Bằng cách sử dụng ThreadLocal để lưu trữ kết nối JDBC, như trong ConnectionHolder trong Liệt kê 3.10, mỗi luồng sẽ có kết nối riêng.

ThreadLocal được sử dụng rộng rãi trong việc thực hiện các khung ứng dụng. Ví dụ, các thùng chứa J2EE liên kết bối cảnh giao dịch với một luồng thực thi trong suốt thời gian của một cuộc gọi EJB. Điều này được thực hiện dễ dàng bằng cách sử dụng một Thread-Local tĩnh giữ bối cảnh giao dịch: khi mã khung cần xác định giao dịch nào đang chạy, nó sẽ tìm nạp bối cảnh giao dịch từ ThreadLocal này. Điều này thuận tiện ở chỗ nó giảm nhu cầu truyền thông tin ngữ cảnh thực thi vào mọi phương thức, nhưng kết hợp bất kỳ mã nào sử dụng cơ chế này vào khung.

Thật dễ dàng để lạm dụng ThreadLocal bằng cách coi thuộc tính giam cầm luồng của nó như một giấy phép để sử dụng các biến toàn cục hoặc như một phương tiện để tạo ra các đối số phương thức ẩn ẩn. Giống như các biến toàn cục, các biến cục bộ có thể làm mất khả năng sử dụng lại và đưa ra các khớp nối ẩn giữa các lớp và do đó nên được sử dụng cẩn thận.


18

Về cơ bản, khi bạn cần một giá trị của biến để phụ thuộc vào luồng hiện tại và nó không thuận tiện để bạn gắn giá trị đó vào luồng theo một cách khác (ví dụ: luồng phân lớp).

Một trường hợp điển hình là một số khung khác đã tạo ra luồng mà mã của bạn đang chạy, ví dụ như một thùng chứa servlet hoặc ở đó nó có ý nghĩa hơn khi sử dụng ThreadLocal vì biến của bạn sau đó "ở vị trí logic" (chứ không phải là một biến treo từ một lớp con Thread hoặc trong một số bản đồ băm khác).

Trên trang web của tôi, tôi có một số thảo luận và ví dụ về thời điểm sử dụng ThreadLocal cũng có thể được quan tâm.

Một số người ủng hộ việc sử dụng ThreadLocal như một cách để gắn "ID luồng" cho mỗi luồng trong các thuật toán đồng thời nhất định khi bạn cần số luồng (xem ví dụ Herlihy & Shavit). Trong những trường hợp như vậy, hãy kiểm tra xem bạn có thực sự nhận được lợi ích không!


ThreadLocal có thể được sử dụng để lưu trữ dấu vết thực thi
gaurav

13
  1. ThreadLocal trong Java đã được giới thiệu trên JDK 1.2 nhưng sau đó đã được khái quát hóa trong JDK 1.5 để giới thiệu loại an toàn trên biến ThreadLocal.

  2. ThreadLocal có thể được liên kết với phạm vi Thread, tất cả các mã được Thread thực thi đều có quyền truy cập vào các biến ThreadLocal nhưng hai luồng không thể nhìn thấy biến ThreadLocal khác.

  3. Mỗi luồng chứa một bản sao độc quyền của biến ThreadLocal đủ điều kiện để thu thập Rác sau khi luồng kết thúc hoặc chết, thông thường hoặc do bất kỳ Ngoại lệ nào, do các biến ThreadLocal đó không có bất kỳ tham chiếu trực tiếp nào khác.

  4. Các biến ThreadLocal trong Java nói chung là các trường tĩnh riêng trong Các lớp và duy trì trạng thái của nó bên trong Thread.

Đọc thêm: ThreadLocal trong Java - Hướng dẫn và chương trình ví dụ


ThreadLocal có thể được sử dụng để lưu trữ dấu vết thực thi
gaurav

11

Tài liệu nói rất rõ: "mỗi luồng truy cập [một biến cục bộ của luồng] (thông qua phương thức get hoặc set) có bản sao được khởi tạo độc lập của biến đó".

Bạn sử dụng một khi mỗi luồng phải có bản sao riêng của một cái gì đó. Theo mặc định, dữ liệu được chia sẻ giữa các chủ đề.


18
Theo mặc định, các đối tượng tĩnh hoặc các đối tượng được truyền rõ ràng giữa các luồng trong đó cả hai luồng sở hữu một tham chiếu đến cùng một đối tượng, được chia sẻ. Các đối tượng được khai báo cục bộ trong một luồng không được chia sẻ (chúng cục bộ với ngăn xếp của luồng). Chỉ muốn làm rõ rằng.
Ian Varley

@IanVarley: Cảm ơn bạn đã chỉ ra điều đó. Vậy nếu chúng ta có biến được khai báo và sử dụng trong lớp MyThread, thì chúng ta có cần ThreadLocal trong trường hợp đó không? Ví dụ: class MyThread mở rộng Chủ đề {Chuỗi var1;, int var2; } trong trường hợp này var1 và var2 sẽ là một phần của ngăn xếp riêng của Thread và không được chia sẻ, vậy chúng ta có yêu cầu ThreadLocal trong trường hợp này không? Tôi có thể hoàn toàn sai trong sự hiểu biết của tôi, xin vui lòng hướng dẫn.
Jayesh

4
Nếu mỗi luồng muốn có một bản sao riêng của một cái gì đó, tại sao nó không thể được khai báo cục bộ (luôn luôn là luồng an toàn)?
sudeepdino008

@ sudeepdino008 bạn không phải lúc nào cũng có quyền kiểm soát việc tạo ra các luồng đó (khung webapp, thư viện
luồng

10

Máy chủ webapp có thể giữ một nhóm luồng và một ThreadLocalvar nên được loại bỏ trước khi phản hồi với máy khách, do đó luồng hiện tại có thể được sử dụng lại theo yêu cầu tiếp theo.


8

Hai trường hợp sử dụng trong đó biến chủ đề có thể được sử dụng -
1- Khi chúng tôi có yêu cầu 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). Điều đó thường xảy ra với một ứng dụng web mà mọi yêu cầu đến một servlet đều có một ID giao dịch duy nhất được liên kết với nó.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

Lưu ý rằng ở đây phương thức withInitial được triển khai bằng biểu thức lambda.
2- Một trường hợp sử dụng khác là khi chúng ta muốn có một cá thể an toàn của luồng và chúng ta không muốn sử dụng đồng bộ hóa vì chi phí hiệu năng với đồng bộ hóa là nhiều hơn. Một trường hợp như vậy là khi SimpleDateFormat được sử dụng. Vì SimpleDateFormat không phải là luồng an toàn nên chúng tôi phải cung cấp cơ chế để làm cho luồng an toàn.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

Tôi vẫn không thấy lợi ích của việc sử dụng ThreadLocal để tạo id duy nhất trong ví dụ nếu tất cả mục đích của bạn là trả về giá trị số nguyên duy nhất tăng dần. `` `lớp công khai ThreadId {// Số nguyên tử chứa ID luồng tiếp theo được gán riêng tĩnh cuối cùng AtomicInteger nextId = new AtomicInteger (0); // Trả về ID duy nhất của luồng hiện tại, gán nó nếu cần thiết int static int get () {return nextId..getAndIncrement (); }} `` `
dashenswen

7

Kể từ khi phát hành Java 8, có nhiều cách khai báo hơn để khởi tạo ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

Cho đến khi phát hành Java 8, bạn phải làm như sau:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

Hơn nữa, nếu phương thức khởi tạo (hàm tạo, phương thức xuất xưởng) của lớp được sử dụng ThreadLocalkhông lấy bất kỳ tham số nào, bạn chỉ có thể sử dụng các tham chiếu phương thức (được giới thiệu trong Java 8):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Lưu ý: Đánh giá là lười biếng vì bạn đang vượt qua java.util.function.Supplierlambda chỉ được đánh giá khi ThreadLocal#getđược gọi nhưng giá trị không được đánh giá trước đó.


5

Bạn phải rất cẩn thận với mẫu ThreadLocal. Có một số mặt trái lớn như Phil đã đề cập, nhưng một khía cạnh không được đề cập là đảm bảo rằng mã thiết lập bối cảnh ThreadLocal không "tái nhập".

Điều tồi tệ có thể xảy ra khi mã đặt thông tin được chạy lần thứ hai hoặc thứ ba vì thông tin trên luồng của bạn có thể bắt đầu biến đổi khi bạn không mong đợi nó. Vì vậy, hãy cẩn thận để đảm bảo thông tin ThreadLocal chưa được đặt trước khi bạn đặt lại.


2
Quyền truy cập lại không phải là vấn đề nếu mã được chuẩn bị để đối phó với nó. Khi nhập, hãy lưu ý xem biến đã được đặt chưa và khi thoát, khôi phục giá trị trước đó (nếu có) hoặc xóa nó (nếu không).
supercat

1
@Jeff, Dude, điều đó đúng với mọi mã bạn viết, không chỉ là ThreadLocalmẫu. Nếu bạn làm F(){ member=random(); F2(); write(member); }và F2 ghi đè thành viên bằng một giá trị mới, thì rõ ràng write(member)sẽ không còn ghi số bạn có random()ed. Đây là nghĩa đen chỉ là lẽ thường. Tương tự, nếu bạn làm F(){ F(); }, thì gd may mắn với vòng lặp vô hạn của bạn! Điều này đúng ở mọi nơi và không cụ thể ThreadLocal.
Pacerier

@Jeff, ví dụ mã?
Pacerier

5

khi nào?

Khi một đối tượng không an toàn cho luồng, thay vì đồng bộ hóa làm cản trở khả năng mở rộng, hãy cung cấp một đối tượng cho mỗi luồng và giữ cho phạm vi luồng, đó là ThreadLocal. Một trong những đối tượng thường được sử dụng nhưng không an toàn luồng là Kết nối cơ sở dữ liệu và JMSConnection.

Làm sao ?

Một ví dụ là Spring framework sử dụng ThreadLocal rất nhiều để quản lý các giao dịch phía sau hậu trường bằng cách giữ các đối tượng kết nối này trong các biến ThreadLocal. Ở mức cao, khi một giao dịch được bắt đầu, nó nhận được kết nối (và vô hiệu hóa cam kết tự động) và giữ nó trong ThreadLocal. trong các cuộc gọi db hơn nữa, nó sử dụng cùng một kết nối để liên lạc với db. Cuối cùng, nó nhận kết nối từ ThreadLocal và cam kết (hoặc khôi phục) giao dịch và giải phóng kết nối.

Tôi nghĩ log4j cũng sử dụng ThreadLocal để duy trì MDC.


5

ThreadLocal là hữu ích, khi bạn muốn có một số trạng thái không nên được chia sẻ giữa các luồng khác nhau, nhưng nó có thể truy cập được từ mỗi luồng trong suốt vòng đời của nó.

Ví dụ, hãy tưởng tượng một ứng dụng web, trong đó mỗi yêu cầu được phục vụ bởi một luồng khác nhau. Hãy tưởng tượng rằng đối với mỗi yêu cầu, bạn cần một phần dữ liệu nhiều lần, khá tốn kém để tính toán. Tuy nhiên, dữ liệu đó có thể đã thay đổi cho mỗi yêu cầu đến, điều đó có nghĩa là bạn không thể sử dụng bộ đệm đơn giản. Một giải pháp đơn giản, nhanh chóng cho vấn đề này sẽ là có một ThreadLocalbiến giữ quyền truy cập vào dữ liệu này, do đó bạn phải tính toán nó một lần cho mỗi yêu cầu. Tất nhiên, vấn đề này cũng có thể được giải quyết mà không cần sử dụng ThreadLocal, nhưng tôi đã nghĩ ra nó cho mục đích minh họa.

Điều đó nói rằng, hãy nhớ rằng ThreadLocalvề cơ bản là một dạng của nhà nước toàn cầu. Kết quả là, nó có nhiều ý nghĩa khác và chỉ nên được sử dụng sau khi xem xét tất cả các giải pháp có thể khác.


ThreadLocals KHÔNG phải là trạng thái toàn cầu trừ khi bạn biến nó thành trạng thái toàn cầu. Họ thực tế luôn luôn có thể truy cập tài nguyên ngăn xếp. Bạn có thể mô phỏng luồng cục bộ chỉ bằng cách chuyển biến đó trong tất cả các phương thức của bạn (bị chậm lại); điều đó không làm cho nó trở thành trạng thái toàn cầu ...
Enerccio

Nó là một dạng của trạng thái toàn cầu, theo nghĩa là nó có thể truy cập từ bất kỳ đâu trong mã (trong cùng ngữ cảnh của luồng). Điều này đi kèm với tất cả các hậu quả, chẳng hạn như không thể suy luận về người đọc và viết giá trị này. Sử dụng một tham số chức năng không phải là một điều chậm trễ, đó là một cách thực hành tốt để thúc đẩy các giao diện sạch. Tuy nhiên, tôi đồng ý với bạn rằng việc truyền một tham số trên toàn bộ chiều sâu của cơ sở mã của bạn là mùi mã. Nhưng, tôi cũng tin rằng trong nhiều trường hợp, việc sử dụng ThreadLocal là mùi mã ở nơi đầu tiên dẫn bạn đến đây, vì vậy đây là điều người ta nên xem xét lại.
Dimos

Nó có thể chỉ đơn giản là một phần của trạng thái đối tượng, không phải được sử dụng trên toàn cầu. Chắc chắn, bạn có được một chút chi phí, nhưng nếu nhiều đối tượng phải có trạng thái khác nhau cho mỗi luồng, bạn có thể sử dụng ThreadLocallàm trường đối tượng ...
Enerccio

Bạn đúng. Có lẽ tôi đã vấp phải một số lạm dụng ThreadLocal, nơi mà nó có thể truy cập được trên toàn cầu. Như bạn đã nói, nó vẫn có thể được sử dụng như một trường lớp giới hạn khả năng hiển thị.
Dimos

4

Không có gì thực sự mới ở đây, nhưng tôi phát hiện ra ngày nay ThreadLocalrất hữu ích khi sử dụng Bean xác thực trong một ứng dụng web. Thông báo xác nhận được bản địa hóa, nhưng theo mặc định sử dụng Locale.getDefault(). Bạn có thể định cấu hình Validatorvới một khác MessageInterpolator, nhưng không có cách nào để chỉ định Localekhi bạn gọi validate. Vì vậy, bạn có thể tạo một tĩnh ThreadLocal<Locale>(hoặc tốt hơn là một thùng chứa chung với những thứ khác mà bạn có thể cần ThreadLocalvà sau đó tùy chỉnh MessageInterpolatorchọn Localetừ đó. Bước tiếp theo là viết một ServletFiltergiá trị sử dụng giá trị phiên hoặc request.getLocale()chọn miền địa phương và lưu trữ nó trong ThreadLocaltài liệu tham khảo của bạn .


4

Như đã được đề cập bởi @unknown (google), cách sử dụng của nó là xác định một biến toàn cục trong đó giá trị được tham chiếu có thể là duy nhất trong mỗi luồng. Việc sử dụng của nó thường đòi hỏi phải lưu trữ một số loại thông tin theo ngữ cảnh được liên kết với chuỗi thực thi hiện tại.

Chúng tôi sử dụng nó trong môi trường Java EE để chuyển danh tính người dùng cho các lớp không biết Java EE (không có quyền truy cập vào httpSession hoặc EJB SessionContext). Bằng cách này, mã, sử dụng nhận dạng cho các hoạt động dựa trên bảo mật, có thể truy cập danh tính từ bất cứ đâu, mà không cần phải chuyển nó một cách rõ ràng trong mọi cuộc gọi phương thức.

Chu kỳ yêu cầu / phản hồi của các hoạt động trong hầu hết các lệnh gọi Java EE làm cho loại sử dụng này trở nên dễ dàng vì nó cung cấp các điểm nhập và thoát được xác định rõ để đặt và bỏ đặt ThreadLocal.


4

ThreadLocal sẽ đảm bảo truy cập đối tượng có thể thay đổi bởi nhiều luồng trong phương thức không đồng bộ được đồng bộ hóa, có nghĩa là làm cho đối tượng có thể thay đổi trở thành bất biến trong phương thức.

Điều này đạt được bằng cách đưa ra phiên bản mới của đối tượng có thể thay đổi cho mỗi luồng thử truy cập vào nó. Vì vậy, nó là bản sao cục bộ cho mỗi chủ đề. Đây là một số hack về việc tạo biến thể hiện trong một phương thức được truy cập như một biến cục bộ. Như bạn biết phương thức biến cục bộ chỉ có sẵn cho luồng, một điểm khác biệt là; các biến cục bộ của phương thức sẽ không có sẵn cho luồng khi quá trình thực thi phương thức kết thúc khi đối tượng có thể thay đổi được chia sẻ với luồng sẽ có sẵn trên nhiều phương thức cho đến khi chúng ta dọn sạch nó.

Theo định nghĩa:

Lớp ThreadLocal trong Java cho phép bạn tạo các biến chỉ có thể được đọc và ghi bởi cùng một luồng. Do đó, ngay cả khi hai luồng đang thực thi cùng một mã và mã có tham chiếu đến biến ThreadLocal, thì hai luồng không thể nhìn thấy các biến ThreadLocal của nhau.

Mỗi Threadtrong java chứa ThreadLocalMaptrong đó.
Ở đâu

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

Đạt được ThreadLocal:

Bây giờ hãy tạo một lớp bao bọc cho ThreadLocal sẽ giữ đối tượng có thể thay đổi như bên dưới (có hoặc không có initialValue()).
Bây giờ getter và setter của trình bao bọc này sẽ hoạt động trên thể hiện luồng thay vì đối tượng có thể thay đổi.

Nếu getter () của threadlocal không tìm thấy bất kỳ giá trị nào trong sơ đồ luồng của Thread; sau đó nó sẽ gọi initValue () để lấy bản sao riêng của nó đối với luồng.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

Bây giờ wrapper.getDateFormatter()sẽ gọi threadlocal.get()và điều đó sẽ kiểm tra currentThread.threadLocalMapchứa này (ThreadLocal) ví dụ.
Nếu có trả về giá trị (SimpleDateFormat) cho thể hiện luồng tương ứng
khác, hãy thêm bản đồ với đối tượng luồng này, initValue ().

Dưới đây là an toàn chủ đề đạt được trên lớp đột biến này; bởi mỗi luồng đang làm việc với thể hiện có thể thay đổi của chính nó nhưng với cùng thể hiện ThreadLocal. Có nghĩa là Tất cả các luồng sẽ chia sẻ cùng một đối tượng ThreadLocal như khóa, nhưng đối tượng SimpleDateFormat khác nhau làm giá trị.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java


3

Các biến chủ đề cục bộ thường được sử dụng để ngăn chia sẻ trong các thiết kế dựa trên các biến Singletons hoặc biến toàn cục.

Nó có thể được sử dụng trong các tình huống như tạo kết nối JDBC riêng biệt cho từng luồng khi bạn không sử dụng Nhóm kết nối.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

Khi bạn gọi getConnection, nó sẽ trả về một kết nối được liên kết với luồng đó. Điều tương tự có thể được thực hiện với các thuộc tính khác như dateformat, bối cảnh giao dịch mà bạn không muốn chia sẻ giữa các luồng.

Bạn cũng có thể đã sử dụng các biến cục bộ cho cùng một thứ, nhưng các tài nguyên này thường chiếm thời gian trong việc tạo, vì vậy bạn không muốn tạo chúng nhiều lần bất cứ khi nào bạn thực hiện logic kinh doanh với chúng. Tuy nhiên, các giá trị ThreadLocal được lưu trữ trong chính đối tượng luồng và ngay khi luồng được thu gom, các giá trị này cũng biến mất.

Liên kết này giải thích việc sử dụng ThreadLocal rất tốt.


2
Trong ví dụ này là một vấn đề lớn: Ai chịu trách nhiệm đóng kết nối? Thay vì tạo kết nối này làm giá trị ban đầu, hãy để người tiêu dùng kết nối tạo kết nối một cách rõ ràng và liên kết nó với luồng thông qua ThreadLocal. Người tạo kết nối sau đó cũng có trách nhiệm đóng cửa. Điều này không rõ ràng trong ví dụ này. Tạo và ràng buộc kết nối cũng có thể được ẩn trong một khung đơn giản bằng một cái gì đó như Transaction.begin () và Transaction.end ().
Rick-Rainer Ludwig

2

Lớp ThreadLocal trong Java cho phép bạn tạo các biến chỉ có thể được đọc và ghi bởi cùng một luồng. Do đó, ngay cả khi hai luồng đang thực thi cùng một mã và mã có tham chiếu đến biến ThreadLocal, thì hai luồng không thể nhìn thấy các biến ThreadLocal của nhau.

Đọc thêm


2

Có 3 kịch bản để sử dụng một trình trợ giúp lớp như SimpleDateFormat trong mã đa luồng, trong đó kịch bản tốt nhất là sử dụng ThreadLocal

Kịch bản

1- Sử dụng đối tượng like share bằng sự trợ giúp của khóa hoặc cơ chế đồng bộ hóa khiến ứng dụng chậm

2- Sử dụng như một đối tượng cục bộ bên trong một phương thức

Trong kịch bản này, nếu chúng ta có 4 luồng, mỗi luồng gọi một phương thức 1000 lần thì chúng ta có
4000 đối tượng SimpleDateFormat được tạo và chờ cho GC xóa chúng

3- Sử dụng ThreadLocal

nếu chúng ta có 4 luồng và chúng ta đã cho mỗi luồng một đối tượng SimpleDateFormat
để chúng ta có 4 luồng , 4 đối tượng của SimpleDateFormat.

Không cần cơ chế khóa và tạo và phá hủy đối tượng. (Độ phức tạp thời gian tốt và độ phức tạp không gian)


2

[Để tham khảo] ThreadLocal không thể giải quyết các vấn đề cập nhật của đối tượng được chia sẻ. Bạn nên sử dụng một đối tượng staticThreadLocal được chia sẻ bởi tất cả các hoạt động trong cùng một luồng. Phương thức [Bắt buộc] remove () phải được các biến ThreadLocal triển khai, đặc biệt là khi sử dụng nhóm luồng trong đó các luồng thường được sử dụng lại. Mặt khác, nó có thể ảnh hưởng đến logic kinh doanh tiếp theo và gây ra sự cố không mong muốn như rò rỉ bộ nhớ.


1

Bộ nhớ đệm, đôi khi bạn phải tính toán cùng một giá trị rất nhiều thời gian để lưu trữ bộ đầu vào cuối cùng cho một phương thức và kết quả là bạn có thể tăng tốc mã. Bằng cách sử dụng Thread Local Storage, bạn tránh phải suy nghĩ về việc khóa.


1

ThreadLocal là một chức năng được cung cấp đặc biệt bởi JVM để cung cấp một không gian lưu trữ riêng biệt cho các luồng. giống như giá trị của biến phạm vi đối tượng chỉ bị ràng buộc với một thể hiện nhất định của một lớp. mỗi đối tượng có các giá trị duy nhất và chúng không thể nhìn thấy giá trị của nhau. khái niệm về các biến ThreadLocal cũng vậy, chúng là cục bộ của luồng theo nghĩa của các đối tượng đối tượng khác, ngoại trừ đối tượng đã tạo ra nó, không thể nhìn thấy nó. Xem tại đây

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

1

Threadlocal cung cấp một cách rất dễ dàng để đạt được khả năng sử dụng lại đối tượng với chi phí bằng không.

Tôi đã có một tình huống trong đó nhiều luồng đang tạo ra một hình ảnh của bộ đệm có thể thay đổi, trên mỗi thông báo cập nhật.

Tôi đã sử dụng một Threadlocal trên mỗi luồng, và sau đó mỗi luồng sẽ chỉ cần đặt lại hình ảnh cũ và sau đó cập nhật lại từ bộ đệm trên mỗi thông báo cập nhật.

Các đối tượng tái sử dụng thông thường từ nhóm đối tượng có chi phí an toàn luồng liên quan đến chúng, trong khi phương pháp này không có.


0

Hãy thử ví dụ nhỏ này, để cảm nhận về biến ThreadLocal:

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


Đầu ra của bảng điều khiển, nếu luồng BookA được thực hiện trước:
Kết quả BookA: 'wordA1, wordA2, wordA3'.
Sách kết quảB: 'wordB1, wordB2'.

Đầu ra của bảng điều khiển, nếu luồng BookB được thực hiện trước:
Kết quả BookB: 'wordB1, wordB2'.
Sách kết quảA: 'wordA1, wordA2, wordA3'.

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.