ThreadLocal trong Java được dùng để tạo ra các biến cục bộ của luồng (thread local variables). Ta biết rằng tất cả các thread của một Object đều chia sẻ chung biến, do đó biến không đảm bảo an toàn đa luồng (thread safe). Ta có thể dùng cơ chế đồng bộ (synchronization) để đảm bảo an toàn cho thread. Nhưng nếu muốn tránh giải pháp đó, ta có thể sử dụng ThreadLocal.
Java ThreadLocal
Mỗi thread sẽ có biến ThreadLocal riêng, và có thể dùng các phương thức get()
và set()
để lấy giá trị mặc định hoặc thay đổi giá trị của biến đó trong phạm vi thread của mình. Các thực thể ThreadLocal thường là các trường mang tính private static
trong những class muốn gắn trạng thái với một thread.
Ví dụ ThreadLocal
Đây là một ví dụ nhỏ minh họa cách sử dụng ThreadLocal trong Java và chứng minh rằng mỗi thread có một bản sao riêng của biến ThreadLocal.
File ThreadLocalExample.java
:
package com.journaldev.threads;
import java.text.SimpleDateFormat;
import java.util.Random;
public class ThreadLocalExample implements Runnable{
// 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 static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
}
}
Kết quả của chương trình ở trên là:
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = M/d/yy h:mm a
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = M/d/yy h:mm a
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 4 formatter = M/d/yy h:mm a
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = M/d/yy h:mm a
Thread Name= 3 formatter = M/d/yy h:mm a
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = M/d/yy h:mm a
Thread Name= 6 formatter = M/d/yy h:mm a
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = M/d/yy h:mm a
Thread Name= 7 formatter = M/d/yy h:mm a
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 9 formatter = M/d/yy h:mm a
Từ kết quả, ta có thể thấy Thread-0 đã thay đổi giá trị của formatter, nhưng formatter của thread-2 vẫn giữ nguyên giá trị khởi tạo. Ta cũng thấy điều tương tự xảy ra với các thread khác.
Class ThreadLocal trong Java 8 đã được mở rộng với một phương thức mới withInitial()
. Nó nhận interface hàm Supplier làm tham số. Do đó, ta có thể dùng biểu thức lambda để tạo thực thể ThreadLocal một cách dễ dàng. Ví dụ, biến ThreadLocal tên formatter ở trên có thể được định nghĩa chỉ trong một dòng như sau:
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.<SimpleDateFormat>withInitial
(() -> {return new SimpleDateFormat("yyyyMMdd HHmm");});
Tổng kết
Nếu bạn quan tâm hơn về chi tiết của ThreadLocal, hãy xem thêm tài liệu chính thức để nắm rõ từng phương thức và ứng dụng thực tế. Ngoài ra, bạn cũng có thể khám phá các bài viết khác về Java 8 trên blog này, nhất là chủ đề về interface hàm.