Статьи

Понимание концепции, стоящей за ThreadLocal

вступление

Я знал о локальных потоках, но до недавнего времени никогда не имел возможности использовать его по-настоящему.
Поэтому я начинаю немного копаться в этой теме, потому что мне нужен был простой способ распространения некоторой пользовательской информации.
через разные слои моего веб-приложения без изменения сигнатуры каждого вызванного метода.

Небольшая предварительная информация

Поток — это отдельный процесс, который имеет свой собственный стек вызовов. В Java имеется один поток на стек вызовов или один стек вызовов на поток. Даже если вы не создаете новые потоки в своей программе, потоки работают без вашего Наглядный пример: когда вы просто запускаете простую Java-программу с помощью метода main, вы неявно не вызываете new Thread (). start (), но JVM создает для вас основной поток, чтобы запустить метод main.

Основной поток является совершенно особенным, потому что это поток, из которого будут порождаться все остальные потоки, и когда
поток завершен, приложение завершает свой жизненный цикл.

На сервере веб-приложений обычно существует пул потоков, потому что поток является классом, достаточно тяжелым для создания. Все серверы JEE (Weblogic, Glassfish, JBoss и т. Д.) Имеют самонастраивающийся пул потоков, что означает, что пул потоков увеличивается и уменьшается когда это необходимо, поэтому поток не создается для каждого запроса, а существующие повторно используются.

Понимание потока локально

Чтобы лучше понять локальность потока, я покажу очень упрощенную реализацию локального пользовательского потока.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package ccs.progest.javacodesamples.threadlocal.ex1;
 
import java.util.HashMap;
import java.util.Map;
 
public class CustomThreadLocal {
 
 private static Map threadMap = new HashMap();
 
 public static void add(Object object) {
  threadMap.put(Thread.currentThread(), object);
 }
 
 public static void remove(Object object) {
  threadMap.remove(Thread.currentThread());
 }
 
 public static Object get() {
  return threadMap.get(Thread.currentThread());
 }
 
}

Таким образом, вы можете в любое время вызывать в своем приложении метод add для CustomThreadLocal, и он будет вставлять в карту текущий поток как ключ и как значение объект, который вы хотите связать с этим потоком. Этот объект может быть объектом, к которому вы хотите иметь доступ из любого места в текущем исполняемом потоке, или это может быть дорогой объект, который вы хотите сохранить связанным с этим потоком и повторно использовать столько раз, сколько захотите.
Вы определяете класс ThreadContext, где у вас есть вся информация, которую вы хотите распространить в потоке.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package ccs.progest.javacodesamples.threadlocal.ex1;
 
public class ThreadContext {
 
 private String userId;
 
 private Long transactionId;
 
 public String getUserId() {
  return userId;
 }
 
 public void setUserId(String userId) {
  this.userId = userId;
 }
 
 public Long getTransactionId() {
  return transactionId;
 }
 
 public void setTransactionId(Long transactionId) {
  this.transactionId = transactionId;
 }
 
 public String toString() {
  return 'userId:' + userId + ',transactionId:' + transactionId;
 }
 
}

Сейчас самое время использовать ThreadContext.

Я начну два потока, и в каждый поток я добавлю новый экземпляр ThreadContext, который будет содержать информацию, которую я хочу распространять для каждого потока.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package ccs.progest.javacodesamples.threadlocal.ex1;
 
public class ThreadLocalMainSampleEx1 {
 
 public static void main(String[] args) {
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = new ThreadContext();
    threadContext.setTransactionId(1l);
    threadContext.setUserId('User 1');
    CustomThreadLocal.add(threadContext);
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = new ThreadContext();
    threadContext.setTransactionId(2l);
    threadContext.setUserId('User 2');
    CustomThreadLocal.add(threadContext);
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
 }
}

Примечание:
CustomThreadLocal.add (threadContext) — это строка кода, в которой текущий поток связан с экземпляром ThreadContext.
Как вы увидите после выполнения этого кода, результат будет:

1
2
userId:User 1,transactionId:1
userId:User 2,transactionId:2

Как это возможно, потому что мы не передали в качестве параметра ThreadContext, userId или trasactionId для printThreadContextValues?

1
2
3
4
5
6
7
package ccs.progest.javacodesamples.threadlocal.ex1;
 
public class PrintThreadContextValues {
 public static void printThreadContextValues(){
  System.out.println(CustomThreadLocal.get());
 }
}

Достаточно просто

Когда CustomThreadLocal.get () вызывается из внутренней карты CustomThreadLocal, он получает объект, связанный с текущим потоком.

Теперь давайте посмотрим примеры, когда используется реальный класс ThreadLocal. (вышеупомянутый класс CustomThreadLocal просто для понимания принципов, лежащих в основе класса ThreadLocal, который очень быстр и оптимально использует память)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package ccs.progest.javacodesamples.threadlocal.ex2;
 
public class ThreadContext {
 
 private String userId;
 private Long transactionId;
 
 private static ThreadLocal threadLocal = new ThreadLocal(){
  @Override
        protected ThreadContext initialValue() {
            return new ThreadContext();
        }
 
 };
 public static ThreadContext get() {
  return threadLocal.get();
 }
 public String getUserId() {
  return userId;
 }
 public void setUserId(String userId) {
  this.userId = userId;
 }
 public Long getTransactionId() {
  return transactionId;
 }
 public void setTransactionId(Long transactionId) {
  this.transactionId = transactionId;
 }
 
 public String toString() {
  return 'userId:' + userId + ',transactionId:' + transactionId;
 }
}

Как описывает javadoc : экземпляры ThreadLocal обычно являются частными статическими полями в классах, которые хотят связать состояние с потоком

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package ccs.progest.javacodesamples.threadlocal.ex2;
 
public class ThreadLocalMainSampleEx2 {
 
 public static void main(String[] args) {
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = ThreadContext.get();
    threadContext.setTransactionId(1l);
    threadContext.setUserId('User 1');
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = ThreadContext.get();
    threadContext.setTransactionId(2l);
    threadContext.setUserId('User 2');
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
 }
}

Когда вызывается get , новый экземпляр ThreadContext связывается с текущим потоком, а затем устанавливаются нужные значения экземпляра ThreadContext.

Как видите, результат такой же, как и для первого набора образцов.

1
2
userId:User 1,transactionId:1
userId:User 2,transactionId:2

(это может быть обратный порядок, поэтому не беспокойтесь, если сначала увидите «Пользователь 2?»)

1
2
3
4
5
6
7
package ccs.progest.javacodesamples.threadlocal.ex2;
 
public class PrintThreadContextValues {
 public static void printThreadContextValues(){
  System.out.println(ThreadContext.get());
 }
}

Еще одно очень полезное использование ThreadLocal — это ситуация, когда у вас есть не поточно-безопасный экземпляр довольно дорогого объекта. Я обнаружил, что наиболее распространенный пример с использованием SimpleDateFormat (но вскоре я приведу другой пример, когда будут использоваться порты веб-сервисов)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package ccs.progest.javacodesamples.threadlocal.ex4;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class ThreadLocalDateFormat {
 // SimpleDateFormat is not thread-safe, so each thread will have one
 private static final ThreadLocal formatter = new ThreadLocal() {
  @Override
  protected SimpleDateFormat initialValue() {
   return new SimpleDateFormat('MM/dd/yyyy');
  }
 };
 public String formatIt(Date date) {
  return formatter.get().format(date);
 }
}

Вывод:

Есть много применений для локальных потоков. Здесь я опишу только два: (Я думаю, что наиболее используемые)

  • Подлинный контекст для каждого потока, такой как идентификатор пользователя или идентификатор транзакции.
  • Экземпляры для потока для производительности.

Ссылка: Понимание концепции ThreadLocal от нашего партнера по JCG Кристиана Чиовари из блога примеров кода Java .