ThreadLocal dùng để làm gì trong Java?

ThreadLocal được sử dụng với một mục đích đơn giản và hiệu quả trong Java. Nó cho phép mỗi thread lưu trữ data riêng của nó và chỉ có thread đó mới truy cập được những data này, các thread khác sẽ không thể truy cập.

Khi nào nên sử dụng ThreadLocal

Một thread là một đơn vị thực thi độc lập, nhiều thread có thể thực thi trên cùng một đoạn code tại cùng một thời điểm. Nếu nhiều thread để thực thi trên cùng một object tại cùng một thời điểm thì chúng phải chia sẻ cùng một object này. Vậy làm sao để mỗi thread có thể sở hữu những object riêng của nó mà các thread khác không thể truy cập? đây chính là lúc chúng ta cần đến ThreadLocal.

Giả sử chúng ta có một HTTP request handler như sau:

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething()
  doSomethingElse()
  renderResponse(resp)
}

Tại điểm bắt đầu của hàm xử lý request chúng ta có thể lấy thông tin của user (người thực hiện request) thông qua hàm getLoggedInUser().

Điều gì sẽ xảy ra nếu hàm doSomething() cần sử dụng thông tin của user? Chúng ta có thể truyền user object vào doSomething() như một tham số đầu vào.

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  service.doSomething(user)
  service.doSomethingElse(user)
  renderResponse(resp,user)
}

Cách làm hoàn toàn có thể giải quyết vấn đề của chúng ta? thế nhưng chúng ta phải lưu ý rằng hàm doSomething() hoặc các hàm khác trong tầng service có thể nhận thêm nhiều tham số nữa và chúng đều có thể muốn hoặc không sử dụng user object. Nếu chúng ta triển khai các method trong các service đều chứa tham số user thì có vẽ không hay!

Đây là lúc chúng ta cần sử dụng đến ThreadLocal.

class StaticClass {
  static private ThreadLocal threadLocal = new ThreadLocal<User>();
  static getThreadLocal() {
    return threadLocal;
  }
}
doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  StaticClass.getThreadLocal().set(user)
  try {
    doSomething()
    doSomethingElse()
    renderResponse(resp)
  }
  finally {
    StaticClass.getThreadLocal().remove()
  }
}

Và giờ đây, ở bất cứ đâu cần sử dụng đến user object thì có thể sử dụng

User user = StaticClass.getThreadLocal().get()

mà không cần phải khai báo thêm bất kỳ tham số đầu vào nào. Chẳng hạn nếu doSomething() cần sử dụng user object thì chỉ việc

public void doSomething() {
   User user = StaticClass.getThreadLocal().get()
    // code here
}

Cách sử dụng ThreadLocal

Chúng ta có thể khởi tạo ThreadLocal thông qua cách sau:

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

Để lưu trữ liệu vào ThreadLocal chúng ta có thể dùng hàm set() và lấy dữ liệu bằng cách sử dụng get().

threadLocalValue.set(1);
Integer result = threadLocalValue.get();

Ngoài ra chúng ta có thể khởi tạo ThreadLocal bằng withInitial() method.

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

Và để xoá dữ liệu trong ThreadLocal chúng ta có thể dùng remove() method.

threadLocal.remove();

Ví dụ chúng ta sẽ lưu trữ user data trong ThreadLocal, và truy cập chúng ở bất cứ đâu trong cùng một thread. 

Trước tiên chúng ta tạo Content class chứa thông tin user.

public class Context {
    private String userName;

    public Context(String userName) {
        this.userName = userName;
    }
}

ThreadLocalWithUserContext implement từ Runnable, mỗi instance của ThreadLocalWithUserContext sẽ được thực thi trong một thread cụ thể riêng lẽ.

public class ThreadLocalWithUserContext implements Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(ThreadLocalWithUserContext.class);
    
    private static final ThreadLocal<Context> userContext = new ThreadLocal<>();
    private final Integer userId;
    private UserRepository userRepository = new UserRepository();

    ThreadLocalWithUserContext(Integer userId) {
        this.userId = userId;
    }


    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        LOG.debug("thread context for given userId: " + userId + " is: " + userContext.get());
    }
}

Và sau đó, chúng ta có thể kiểm thử bằng đoạn mã sau: 

ThreadLocalWithUserContext firstUser 
  = new ThreadLocalWithUserContext(1);
ThreadLocalWithUserContext secondUser 
  = new ThreadLocalWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

Output

thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}

Chúng ta có thể thấy rằng mỗi user có một content riêng của mình. Tất cả các method khác chứa trong ThreadLocalWithUserContext đều có thể lấy dữ liệu từ TheadLocal.

ThreadLocal và Thread Pool

ThreadLocal cung cấp các API đơn giản cho phép lưu trữ các dữ liệu riêng lẽ cho từng thread. Tuy nhiên chúng ta cần cẩn trọng khi sử dụng ThreadLocal và Thread Pools.

Chúng ta đều biết Thread Pool có nhiệm vụ tạo và truy trì một số lượng thread nhất định trong ứng dụng. Ứng dụng có thể dùng sử dụng một thread nhanh chóng bằng cách nhận một thread từ thread pool. Quá trình này sẽ đi qua một số bước:

  • Đầu tiên, ứng dụng nhận một thread từ thread pool.
  • Tiếp theo, ứng dụng sẽ lưu trữ một số dữ liệu riêng cho thread này với ThreadLocal.
  • Khi quá trình thực thi hoàn tấtm ứng dụng sẽ trả thread lại cho thread pool.
  • Sau một khoảng thời gian, ứng dụng lại thực thi và mượn cùng một thread đã mượn ban đầu.

Nếu trước đó ứng dụng không thực hiện các thao tác dọn dẹp dữ liệu đã lưu vào thread bằng ThreadLocal thì có thể trong lần thực thi kế tiếp những dữ liệu này sẽ được sử dụng lại.

Vì vậy chúng ta nên nên dọn dẹp dữ liệu trong ThreadLocal sau khi thực thi xong bằng cách sử dụng remove() method.

Nguồn tham khảo

https://dzone.com/articles/purpose-threadlocal-java-and

https://www.baeldung.com/java-threadlocal

0 0 votes
Article Rating
Subscribe
Notify of
guest
1 Comment
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
1
0
Would love your thoughts, please comment.x
()
x