8 Ocak 2020 Çarşamba

ThreadLocal Sınıfı

Giriş
Şu satırı dahil ederiz
import java.lang.ThreadLocal;
Sınıf her zaman static final olarak tanımlamakta fayda var. Şöyle yaparız.
private static final ThreadLocal<Foo> fooHolder = new ThreadLocal<Foo>() {...}
Açıklaması şöyle
ThreadLocal allows you to associate a per-thread value with a value-holding object. It allows you to store different objects for different threads and maintains which object corresponds to which thread. It has set and get accessor methods which maintain a separate copy of the value for each thread that uses it. The  get() method always returns the most updated value passed to  set() from the currently executing thread.
Sonar Uyarısı
Açıklaması şöyle
"ThreadLocal" variables should be cleaned up when no longer used

ThreadLocal variables are supposed to be garbage collected once the holding thread is no longer alive. Memory leaks can occur when holding threads are re-used which is the case on application servers using pool of threads.

To avoid such problems, it is recommended to always clean up ThreadLocal variables using the remove() method to remove the current thread’s value for the ThreadLocal variable.

In addition, calling set(null) to remove the value might keep the reference to this pointer in the map, which can cause memory leak in some scenarios. Using remove is safer to avoid this issue.
Aslında bu problem daha çok Application Server kullanırken ortaya çıkıyor. Çünkü Application Server thread'leri ölmüyor. Dolayısıyla bir deployment unload edilse bile bellekten silinmiyor. Açıklaması şöyle
ThreadLocals are supposed to be garbage collected once the holding thread is no longer alive. But the problem arises when we use ThreadLocals along with modern application servers.

Modern application servers use a pool of threads to process requests, instead of creating new ones (for example, the Executor in the case of Apache Tomcat). Moreover, they also use a separate classloader.

Since Thread Pools in application servers work on the concept of thread reuse, they're never garbage collected; instead, they're reused to serve another request.

If any class creates a ThreadLocal variable, but doesn't explicitly remove it, then a copy of that object will remain with the worker Thread even after the web application is stopped, thus preventing the object from being garbage collected.
Örnek
Sonar kuralna göre ThreadLocal ile işimiz bitince remove() çağrısı yapmayan kod şöyle
// Noncompliant Code Example
public class ThreadLocalUserSession implements UserSession {

  private static final ThreadLocal<UserSession> DELEGATE = new ThreadLocal<>();

  public UserSession get() {
    UserSession session = DELEGATE.get();
    if (session != null) {
      return session;
    }
    throw new UnauthorizedException("User is not authenticated");
  }

  public void set(UserSession session) {
    DELEGATE.set(session);
  }

  public void incorrectCleanup() {
    DELEGATE.set(null); // Noncompliant
  }
  // some other methods without a call to DELEGATE.remove()
}
Önerilen kod şöyle
// Compliant Solution
public class ThreadLocalUserSession implements UserSession {

  private static final ThreadLocal<UserSession> DELEGATE = new ThreadLocal<>();

  public UserSession get() {
    UserSession session = DELEGATE.get();
    if (session != null) {
      return session;
    }
    throw new UnauthorizedException("User is not authenticated");
  }

  public void set(UserSession session) {
    DELEGATE.set(session);
  }

  public void unload() {
    DELEGATE.remove(); // Compliant
  }
  // ...
}
Test - Global ThreadLocal nesnesi
Şöyle yaparız. 0-4 arası çıktı görebiliriz. Yani her thread global bir ThreadLocal nesnesi olsa bile kendi değerini görüyor.
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int index = 0; index < 5; index++) {
  final int value = index;
  executorService.submit(() -> {
    threadLocal.set(value);
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      ...
    }
    System.out.println(" = " + threadLocal.get());
  });
}

initialValue metodu
withInitial() metodu tercih edilebilir. Bu metod protected. ThreadLocal nesnesine değer atamak için çağrılır. 
Örnek
Şöyle yaparız
static final ThreadLocal<MyParser> PARSER = new ThreadLocal<MyParser>() {
    @Override
    protected MyParser initialValue() {
        return new MyParser();
    }
};
Örnek
ThreadLocal sınıfı SimpleDateFormat, DecimalFormat gibi thread-safe olmayan nesnelerle beraber sıkça kullanılır. Şöyle yaparız.
// SimpleDateFormat is not thread-safe, so give one to each thread
static final ThreadLocal<SimpleDateFormat> f= new ThreadLocal<SimpleDateFormat>(){
  @Override
  protected SimpleDateFormat initialValue(){
    return new SimpleDateFormat("yyyyMMdd HHmm");
  }
};
Daha sonra bu nesne şöyle kullanılıyor.
public String formatIt(Date date) {
  return f.get().format(date);
}
get metodu
Açıklaması şöyle.
Returns the value in the current thread's copy of this thread-local variable.
Bu metod public'tir. ThreadLocal nesnemiz static final olduğu için şöyle yaparız.
void parse(String input) {
    PARSER.get().parse(input);
}
remove metodu
ThreadLocal nesnemiz static final olduğu için şöyle yaparız.
public static void unset() {
  PARSER.remove();
}
withInitial metodu - Supplier

Şöyle yaparız.
ThreadLocal<Map<K, V>> safeMap = ThreadLocal.withInitial(() -> {...});
Açıklaması şöyle. Yan remove() ile değeri silsek bile tekrar withInitial ile verile değer ile başlarız.
 This method will be invoked the first time a thread accesses the variable with the get() method, unless the thread previously invoked the set(T) method, in which case the initialValue method will not be invoked for the thread. Normally, this method is invoked at most once per thread, but it may be invoked again in case of subsequent invocations of remove() followed by get().


Hiç yorum yok:

Yorum Gönder