Оригинал статьи, обязательно поместите следующий абзац в начале статьи (сохраните гиперссылку) для перепечатки.
Эта статья отправлена измир технологий,Оригинальная ссылкаWoohoo. Джейсон GJ.com/Java/thread…
Какую проблему решает ThreadLocal
Поскольку ThreadLocal поддерживает универсальные типы, такие как ThreadLocal, для удобства выражения в следующем тексте используетсяПеременнаяПредставляет сам ThreadLocal при использованиипримерПредставляет экземпляр конкретного типа, такого как StringBuilder .
неправильное понимание
Одной из причин написания этой статьи является то, что многие блоги в Интернете описывают применимые сценарии ThreadLocal и проблемы, которые он решает, и описания неясны или даже неверны. Ниже приведено общее введение в ThreadLocal.
ThreadLocal предлагает новую идею для решения проблемы параллелизма многопоточных программ.
Цель ThreadLocal — решить проблему совместного использования, когда несколько потоков обращаются к ресурсам.
Есть также много статей, в которых сравниваются сходства и различия между ThreadLocal и синхронизацией. Поскольку это сравнение, следует учитывать, что они решают одни и те же или похожие проблемы.
Проблема с приведенным выше описанием заключается в том, что ThreadLocal не решает проблему многопоточности.общийпеременная проблема. Поскольку переменные не являются общими, проблем с синхронизацией не возникает.
разумное понимание
ThreadLoal, ее основной принцип заключается в том, что объекты, содержащиеся в одном и том же ThreadLocal (для ThreadLocal
- Потому что у каждого потока есть своя копия экземпляра, и эта копия может использоваться только текущим потоком. Это также является источником названия ThreadLocal.
- Поскольку у каждого потока есть собственная копия экземпляра, а другие потоки недоступны, нет проблем с разделением между несколькими потоками.
- Если нет обмена, в чем проблема синхронизации и как решить проблему синхронизации?
Итак, какую проблему решает ThreadLocal и к каким сценариям она применима?
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
основной смысл
ThreadLocal предоставляет экземпляры локальных переменных потока. Она отличается от обычной переменной тем, что каждый поток, использующий эту переменную, инициализирует полностью независимую копию экземпляра. Переменные ThreadLocal обычно
private static
ретушь. Когда поток завершается, все использованные копии экземпляра, связанные с ThreadLocal, могут быть повторно использованы.
В основном,ThreadLocal подходит для сценариев, где каждому потоку нужен собственный независимый экземпляр, и этот экземпляр необходимо использовать в нескольких методах, то есть переменные изолированы между потоками и совместно используются методами или классами.Эта точка зрения будет разработана с примерами позже. Кроме того, в этом сценарии нет необходимости использовать ThreadLocal, другие способы могут достичь того же эффекта, но ThreadLocal делает реализацию более лаконичной.
Использование ThreadLocal
пример кода
Следующий код иллюстрирует использование ThreadLocal
public class ThreadLocalDemo {
public static void main(String[] args) throws InterruptedException {
int threads = 3;
CountDownLatch countDownLatch = new CountDownLatch(threads);
InnerClass innerClass = new InnerClass();
for(int i = 1; i <= threads; i++) {
new Thread(() -> {
for(int j = 0; j < 4; j++) {
innerClass.add(String.valueOf(j));
innerClass.print();
}
innerClass.set("hello world");
countDownLatch.countDown();
}, "thread - " + i).start();
}
countDownLatch.await();
}
private static class InnerClass {
public void add(String newStr) {
StringBuilder str = Counter.counter.get();
Counter.counter.set(str.append(newStr));
}
public void print() {
System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString());
}
public void set(String words) {
Counter.counter.set(new StringBuilder(words));
System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString());
}
}
private static class Counter {
private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
return new StringBuilder();
}
};
}
}
Анализ случая
ThreadLocal изначально поддерживает дженерики. В этом примере используется переменная ThreadLocal типа StringBuilder. Экземпляр StringBuidler можно прочитать с помощью метода get() класса ThreadLocal, а StringBuilder также можно установить с помощью метода set(T t).
Результат выполнения приведенного выше кода выглядит следующим образом
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:012
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1362597339, Value:hello world
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0123
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:482932940, Value:hello world
Set, Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1691922941, Value:hello world
Как видно из приведенного выше вывода
- 1-3 видно из выходной линии, каждая нить через метод ThreadLocal Get () для получения различных экземпляров StringBuilder
- Вывод строк 1-3 показывает, что каждый поток обращается к одной и той же переменной ThreadLocal.
- Это видно из вывода строк 7, 12, 13 и кода строки 30, хотя код должен получить() поле статического счетчика класса Counter, чтобы получить экземпляр StringBuilder и добавить строку, но это не так. не добавлять все потоки. Все строки помещаются в один и тот же StringBuilder, но каждый поток добавляет строку к своему собственному экземпляру StringBuidler.
- Сравнивая вывод строки 1 и строки 15 и комбинируя код строки 38, мы видим, что после использования метода set(T t) экземпляр StringBuilder, на который указывает переменная ThreadLocal, заменяется
Принцип ThreadLocal
ThreadLocal поддерживает сопоставление потоков и экземпляров.
Поскольку каждый поток, обращающийся к переменной ThreadLocal, имеет свою собственную копию «локального» экземпляра. Возможное решение для ThreadLocal состоит в том, чтобы поддерживать карту с ключом, являющимся потоком, и значением, являющимся его экземпляром в этом потоке. Когда поток получает экземпляр с помощью схемы get() класса ThreadLocal, ему достаточно использовать поток в качестве ключа для поиска соответствующего экземпляра на карте. Схема представлена на рисунке ниже
Эта схема удовлетворяет требованию независимой резервной копии в каждом упомянутой выше ните. Каждый новый доступен для доступа к потоку, необходимо добавить карту на карту, и когда каждая нить заканчивается, карта должна быть очищена. Здесь есть две проблемы:
- Добавление потоков и сокращение потоков требует написания карты, поэтому необходимо убедиться, что карта является потокобезопасной. несмотря на то чтоЗнакомство с базовой технологией многопоточности Java из эволюции ConcurrentHashMapВ этой статье представлено несколько способов реализации потокобезопасной карты, но она более или менее требует блокировок для обеспечения безопасности потоков.
- Когда поток завершается, он должен убедиться, что все соответствующие сопоставления в ThreadLocal, к которому он обращается, удалены, иначе это может привести к утечке памяти. (Позже мы представим способы избежать утечек памяти)
Проблема блокировок — одна из причин, по которой JDK не приняла эту схему.
Thread поддерживает сопоставление ThreadLocal и экземпляров
В приведенном выше решении проблема выглядит как блокировка, поскольку несколько потоков обращаются к одной и той же карте. Если карта поддерживается потоком, так что каждый поток имеет доступ только к своей собственной карте, то проблема записи многопоточной, нет никакой необходимости в блокировке не существует. Вариант осуществления, показанный на фиг.
Хотя в этой схеме нет проблем с блокировкой, после того, как каждый поток обращается к переменной ThreadLocal, он будет поддерживать сопоставление между переменной ThreadLocal и конкретным экземпляром в своей собственной карте.Если эти ссылки (карты) не удалены, эти ThreadLocals не могут быть переработано. , может вызвать утечку памяти. Позже мы расскажем, как JDK решает эту проблему.
Реализация ThreadLocal в JDK 8
ThreadLocalMap и утечка памяти
В этой схеме Map предоставляется статическим внутренним классом ThreadLocalMap класса ThreadLocal. Экземпляры этого класса поддерживают сопоставление ThreadLocal с конкретным экземпляром. В отличие от HashMap, каждая запись ThreadLocalMap представляет собой паруключСлабая ссылка, эта точка изsuper(k)
можно увидеть. Кроме того, каждая запись содержит парустоимостьсильные цитаты.
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Причина использования слабых ссылок заключается в том, что, когда нет строгой ссылки на переменную ThreadLocal, ее можно повторно использовать, что позволяет избежать проблемы утечки памяти, из-за которой ThreadLocal нельзя повторно использовать, как описано выше.
Однако здесь может быть другая проблема с утечкой памяти. ThreadLocalMap поддерживает сопоставление между переменными ThreadLocal и конкретными экземплярами.При повторном использовании переменной ThreadLocal ключ карты становится нулевым, и запись не может быть удалена. В результате запись ссылается на экземпляр и не может быть повторно использована, что приводит к утечке памяти.
Примечание:Хотя Entry является слабой ссылкой, это слабая ссылка типа Threadlocal (то есть, это пара.ключСлабые ссылки), а не слабые ссылки на конкретные экземпляры, поэтому нельзя избежать утечек памяти, связанных с конкретными экземплярами.
чтение экземпляра
Метод чтения экземпляра выглядит так
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
При чтении экземпляра поток сначала проходитgetMap(t)
Метод получает собственный ThreadLocalMap. Из определения метода ниже видно, что экземпляр ThreadLocalMap является полем класса Thread, то есть Thread поддерживает сопоставление между объектом ThreadLocal и конкретным экземпляром, что согласуется с приведенным выше анализом.
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
После получения ThreadLocalMap перейдитеmap.getEntry(this)
Метод получает соответствующую запись ThreadLocal в ThreadLocalMap текущего потока. Это в методе является текущим доступом к объекту ThreadLocal.
Если полученная запись не является нулевой, значение, взятое из записи, является экземпляром, соответствующим потоку, к которому необходимо получить доступ. Если полученная запись равна нулю, перейдитеsetInitialValue()
Метод устанавливает начальное значение переменной ThreadLocal, соответствующее конкретному экземпляру в потоке.
установить начальное значение
Способ установки начального значения следующий
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Этот метод является закрытым и не может быть перегружен.
Во-первых, поinitialValue()
метод получения начального значения. Этот метод общедоступен и по умолчанию возвращает null. Поэтому этот метод часто перегружается при обычном использовании. В приведенном выше примере он перегружен во внутреннем анонимном классе.
Затем получите объект ThreadLocalMap, соответствующий потоку. Если объект не равен нулю, напрямую добавьте сопоставление между объектом ThreadLocal и начальным значением соответствующего экземпляра в ThreadLocalMap потока. Если значение null, объект ThreadLocalMap создается перед добавлением в него карты.
Здесь нет необходимости рассматривать потокобезопасность ThreadLocalMap. Поскольку каждый поток имеет один и только один объект ThreadLocalMap, и только сам поток может получить к нему доступ, другие потоки не будут иметь доступа к ThreadLocalMap, то есть объект не будет совместно использоваться несколькими потоками, и проблема безопасности потоков не возникает.
установить экземпляр
кроме как черезinitialValue()
Метод Установка начального значения экземпляра, а также можно установить значение экземпляра потока с помощью метода SET, как показано ниже.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
Этот метод сначала получает объект ThreadLocalMap потока, а затем напрямую добавляет сопоставление между объектом ThreadLocal (то есть this в коде) и целевым экземпляром в ThreadLocalMap. Конечно, если сопоставление уже существует, оно будет перезаписано напрямую. Кроме того, если полученный объект ThreadLocalMap имеет значение null, сначала создается объект ThreadLocalMap.
предотвратить утечку памяти
Для объекта ThreadLocal, который больше не используется и был повторно использован, его соответствующий экземпляр в каждом потоке не может быть повторно использован, поскольку на него строго ссылается Entry ThreadLocalMap потока, что может вызвать утечку памяти.
В ответ на эту проблему в методе set ThreadLocalMap значение всех Entry, чей ключ имеет значение null, устанавливается равным null с помощью метода replaceStaleEntry, чтобы значение можно было использовать повторно. Кроме того, в методе повторного хеширования метод expungeStaleEntry установит для ключа и нулевого значения записи значение null, чтобы запись можно было повторно использовать. Таким образом, ThreadLocal предотвращает утечку памяти.
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
Применимая сцена
Как упоминалось выше, ThreadLocal подходит для следующих двух сценариев.
- Каждый поток должен иметь свой отдельный экземпляр
- Экземпляры должны быть разделены между несколькими методами, но не ожидается, что они будут совместно использоваться несколькими потоками.
Во-первых, у каждого потока есть свой экземпляр, есть много способов его реализовать. Например, внутри потока может быть создан отдельный экземпляр. ThreadLoca может удовлетворить эту потребность в очень удобной форме.
Для второго пункта он может быть реализован в виде передачи по ссылке между методами при условии, что первый пункт (каждый поток имеет свой экземпляр) выполняется. ThreadLocal делает код менее связанным, а реализацию более элегантной.
кейс
Для веб-приложений Java сеанс содержит много информации. Во многих случаях необходимо получить информацию через сеанс, а иногда необходимо изменить информацию сеанса. С одной стороны, вам нужно убедиться, что у каждого потока есть свой отдельный экземпляр Session. С другой стороны, поскольку Session необходимо манипулировать во многих местах, необходимо несколько методов для совместного использования Session. Вместо использования ThreadLocal вы можете создать экземпляр Session для каждого потока и передать этот экземпляр между методами, как показано ниже.
public class SessionHandler {
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public Session createSession() {
return new Session();
}
public String getUser(Session session) {
return session.getUser();
}
public String getStatus(Session session) {
return session.getStatus();
}
public void setStatus(Session session, String status) {
session.setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
Session session = handler.createSession();
handler.getStatus(session);
handler.getUser(session);
handler.setStatus(session, "close");
handler.getStatus(session);
}).start();
}
}
Этот метод достижим. Но везде, где необходимо использовать Session, объект Session необходимо передавать явно, а связь между методами высока.
Здесь функция повторно реализована с использованием ThreadLocal, как показано ниже.
public class SessionHandler {
public static ThreadLocal<Session> session = new ThreadLocal<Session>();
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public void createSession() {
session.set(new Session());
}
public String getUser() {
return session.get().getUser();
}
public String getStatus() {
return session.get().getStatus();
}
public void setStatus(String status) {
session.get().setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
handler.getStatus();
handler.getUser();
handler.setStatus("close");
handler.getStatus();
}).start();
}
}
Модифицированному коду, использующему ThreadLocal, больше не нужно передавать объекты Session между методами, а также очень легко обеспечить, чтобы каждый поток имел свой собственный независимый экземпляр.
Если вы посмотрите только на один из этих пунктов, есть много альтернатив. Например, каждый поток может иметь свой собственный экземпляр, создавая локальные переменные в потоке, а использование статических переменных может реализовать совместное использование переменных между методами. Но если вы хотите одновременно обеспечить изоляцию переменных между потоками и совместное использование методов, больше подойдет ThreadLocal.
Суммировать
- ThreadLocal не решает проблему обмена данными между потоками
- ThreadLocal позволяет избежать проблем с потокобезопасностью экземпляра, неявно создавая отдельные копии экземпляра в разных потоках.
- Каждый поток содержит карту и поддерживает сопоставление между объектами ThreadLocal и конкретными экземплярами.Поскольку к карте обращается только поток, который ее удерживает, нет проблем с безопасностью потоков и блокировкой.
- Ссылка на ThreadLocal по записи ThreadLocalMap является слабой ссылкой, что позволяет избежать проблемы, связанной с невозможностью повторного использования объектов ThreadLocal.
- Метод set в ThreadLocalMap предотвращает утечку памяти, вызывая метод replaceStaleEntry для восстановления значения объекта Entry, ключ которого равен null (то есть конкретного экземпляра), и самого объекта Entry.
- ThreadLocal подходит для сценариев, в которых переменные изолированы между потоками и совместно используются методами.
Расширенная серия Java
- Java Advanced (1) Аннотация (аннотация)
- Java Advanced (2) Когда мы говорим о безопасности потоков, о чем мы говорим?
- Java Advanced (3) Ключевые технологии многопоточной разработки
- Между потоками сравнения Java Advanced (IV)
- Расширенный режим Java (5) Расширенный режим NIO и Reactor
- Java Advanced (6) Взгляните на основную технологию многопоточности Java на основе эволюции ConcurrentHashMap.
- Java Advanced (7) Правильно понимать принцип и применимые сценарии Thread Local