prefacio
Ya sea trabajo o entrevista , nos ThreadLocal
ocuparemos de usted, hablemos con usted hoy ThreadLocal
~
- ¿Qué es ThreadLocal?Por qué usar ThreadLocal
- Un caso de uso de ThreadLocal
- El principio de ThreadLocal
- ¿Por qué no usar la identificación del hilo directamente como la clave de ThreadLocalMap?
- ¿Por qué causa una pérdida de memoria? ¿Es por referencias débiles?
- ¿Por qué Key está diseñado como una referencia débil? ¿No se puede hacer una referencia fuerte?
- InheritableThreadLocal garantiza datos compartidos entre subprocesos principales y secundarios
- Escenarios de aplicación y notas para usar ThreadLocal
1. ¿Qué es ThreadLocal?¿Por qué usar ThreadLocal?
¿Qué es ThreadLocal?
ThreadLocal
, una variable local de subproceso. Si crea una ThreadLocal
variable, cada subproceso que acceda a esta variable tendrá una copia local de la variable. Cuando varios subprocesos operan en esta variable, en realidad están operando en la variable en su propia memoria local, logrando así el aislamiento del subproceso. Función, evitando problemas de seguridad de subprocesos en escenarios simultáneos.
// 创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
Por qué usar ThreadLocal
En un escenario simultáneo, habrá un escenario en el que varios hilos modifiquen una variable compartida al mismo tiempo. Esto puede presentar un problema de seguridad lineal .
Para resolver problemas de seguridad lineal, puede usar métodos de bloqueo, como usar synchronized
o Lock
. Pero la forma de bloqueo puede hacer que el sistema se ralentice. El diagrama esquemático del bloqueo es el siguiente:
Hay otra solución, que es usar espacio por tiempo, es decir, usar ThreadLocal
. Cuando se usa ThreadLocal
una clase para acceder a una variable compartida, una copia de la variable compartida se guarda localmente en cada subproceso. Cuando varios subprocesos modifican una variable compartida, en realidad operan en una copia de la variable, lo que garantiza la seguridad lineal.
2. Un caso de uso de ThreadLocal
En el desarrollo diario, ThreadLocal
a menudo aparece en la clase de herramienta de conversión de fechas. Echemos un vistazo a un contraejemplo :
/**
* 日期工具类
*/
public class DateUtil {
private static final SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String dateString) {
Date date = null;
try {
date = simpleDateFormat.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
Ejecutamos DateUtil
esta clase de utilidad en un entorno de subprocesos múltiples:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
});
}
executorService.shutdown();
}
Después de ejecutar, encontré un error:
Si DateUtil
agrega y ejecuta en la clase de herramienta ThreadLocal
, no tendrá este problema:
/**
* 日期工具类
*/
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date parse(String dateString) {
Date date = null;
try {
date = dateFormatThreadLocal.get().parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
});
}
executorService.shutdown();
}
}
复制代码
resultado de la operación:
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
复制代码
En el contraejemplo de ahora , ¿por qué informa un error? Esto se debe a que SimpleDateFormat
no es linealmente seguro. Cuando aparece como una variable compartida, se informará un error en un escenario de subprocesamiento múltiple concurrente.
¿Por qué no hay problema si lo agregas ThreadLocal
? En el escenario concurrente, ThreadLocal
¿cómo se garantiza? Veamos los principios básicos a continuación ThreadLocal
.
3. El principio de ThreadLocal
3.1 Diagrama de estructura de memoria de ThreadLocal
Para tener una comprensión macro, primero veamos ThreadLocal
el diagrama de estructura de memoria a continuación
Desde el diagrama de estructura de memoria, podemos ver:
Thread
En la clase, hay unaThreadLocal.ThreadLocalMap
variable miembro de .ThreadLocalMap
Una matriz se mantiene internamenteEntry
, cada uno de los cualesEntry
representa un objeto completo,key
que esThreadLocal
en sí mismo, yvalue
esThreadLocal
un valor de objeto genérico.
3.2 Análisis de código fuente clave
En comparación con varios códigos fuente clave, es más fácil de entender ~ Volvamos al Thread
código fuente de la clase, y puede ver que ThreadLocalMap
el valor inicial de la variable miembro esnull
public class Thread implements Runnable {
// ThreadLocal.ThreadLocalMap是Thread的属性
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap
El código fuente clave es el siguiente:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//Entry数组
private Entry[] table;
// ThreadLocalMap的构造器,ThreadLocal作为key
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
ThreadLocal
set()
Métodos clave en la clase :
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程t
ThreadLocalMap map = getMap(t); //根据当前线程获取到ThreadLocalMap
if (map != null) //如果获取的ThreadLocalMap对象不为空
map.set(this, value); //K,V设置到ThreadLocalMap中
else
createMap(t, value); //创建一个新的ThreadLocalMap
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //返回Thread对象的ThreadLocalMap属性
}
void createMap(Thread t, T firstValue) { //调用ThreadLocalMap的构造函数
t.threadLocals = new ThreadLocalMap(this, firstValue); this表示当前类ThreadLocal
}
}
ThreadLocal
get()
método clave en la clase
public T get() {
Thread t = Thread.currentThread();//获取当前线程t
ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap
if (map != null) { //如果获取的ThreadLocalMap对象不为空
//由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //初始化threadLocals成员变量的值
}
private T setInitialValue() {
T value = initialValue(); //初始化value的值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //以当前线程为key,获取threadLocals成员变量,它是一个ThreadLocalMap
if (map != null)
map.set(this, value); //K,V设置到ThreadLocalMap中
else
createMap(t, value); //实例化threadLocals成员变量
return value;
}
Entonces, ¿cómo responder al principio de implementación de ThreadLocal ? De la siguiente manera, es mejor explicarlo junto con el diagrama de estructura anterior ~
Thread
La clase hilo tiene unaThreadLocal.ThreadLocalMap
variable de instancia de tipothreadLocals
, es decir, cada hilo tiene uno propioThreadLocalMap
.ThreadLocalMap
Una matriz se mantiene internamenteEntry
, cada uno de los cualesEntry
representa un objeto completo,key
que esThreadLocal
en sí mismo, yvalue
esThreadLocal
un valor genérico.- En el escenario de subprocesos múltiples concurrentes, cada subproceso
Thread
, al establecer un valorThreadLocal
en él ,ThreadLocalMap
lo almacena en su propia memoria y utiliza una referencia como referencia para que cada subproceso encuentre el valor correspondienteThreadLocal
en su propia memoria , logrando así el aislamiento del subproceso .map
key
Después de comprender estos métodos básicos, algunos amigos pueden tener dudas, ¿ ThreadLocalMap
por qué deberían usarse ThreadLocal
como claves? 线程Id
¿ Es diferente usarlo directamente ?
4. ¿Por qué no usar la identificación del hilo directamente como la clave de ThreadLocalMap?
Por ejemplo, el código es el siguiente:
public class TianLuoThreadLocalTest {
private static final ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
private static final ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
}
Este escenario: una clase de uso tiene dos variables compartidas, es decir, ThreadLocal
se utilizan dos variables miembro. Si se id
usan subprocesos , ¿cómo distinguir qué variable miembro? Por lo tanto, aún debe usarse como . Cada objeto se puede distinguir de forma única por atributos , y cada objeto ThreadLocal se puede distinguir de forma única por el nombre del objeto ( ejemplo a continuación ). Mira el código:ThreadLocalMap
key
ThreadLocal
ThreadLocal
Key
ThreadLocal
threadLocalHashCode
ThreadLocal
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
Entonces veamos el siguiente ejemplo de código:
public class TianLuoThreadLocalTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable(){
public void run(){
ThreadLocal<TianLuoDTO> threadLocal1 = new ThreadLocal<>();
threadLocal1.set(new TianLuoDTO("Hello 七度"));
System.out.println(threadLocal1.get());
ThreadLocal<TianLuoDTO> threadLocal2 = new ThreadLocal<>();
threadLocal2.set(new TianLuoDTO("你好:ccc"));
System.out.println(threadLocal2.get());
}});
t.start();
}
}
// 运行结果
TianLuoDTO{name='Hello 七度'}
TianLuoDTO{name='你好:ccc'}
Puede ser más claro si lo comparas con esta imagen:
5. ¿Por qué TreadLocal provoca pérdidas de memoria?
5.1 ¿Qué pasa con las fugas de memoria causadas por referencias débiles?
Primero echemos un vistazo al diagrama de referencia de TreadLocal:
Con respecto a la fuga de memoria de ThreadLocal, la declaración más popular en Internet es esta:
ThreadLocalMap
La referencia débil utilizada como , cuando la variable se configura manualmente , es decir, no hay una referencia fuerte externa para referirse a ella, cuando el sistema GCThreadLocal
debe reciclarse. En este caso, habrá objetos en el medio , y no hay forma de acceder a estos objetos.Si el subproceso actual no finaliza durante mucho tiempo (como el subproceso central del grupo de subprocesos), siempre habrá un fuerte cadena de referencia :estos objetosparakey
ThreadLocal
null
ThreadLocal
ThreadLocal
ThreadLocalMap
key
null
Entry
key
null
Entry
value
key
null
Entry
value
Cuando la variable ThreadLocal se establece manualmente en null
el diagrama de cadena de referencia:
De hecho, ThreadLocalMap
esta situación se ha tenido en cuenta en el diseño de . Por lo tanto, también se agregan algunas medidas de protección: es decir ThreadLocal
, get
el set
método remove
borrará ThreadLocalMap
todas key
las acciones en el hilo .null
value
En el código fuente, se refleja, como ThreadLocalMap
el set
método:
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;
}
//如果k等于null,则说明该索引位之前放的key(threadLocal对象)被回收了,这通常是因为外部将threadLocal变量置为null,
//又因为entry对threadLocal持有的是弱引用,一轮GC过后,对象被回收。
//这种情况下,既然用户代码都已经将threadLocal置为null,那么也就没打算再通过该对象作为key去取到之前放入threadLocalMap的value, 因此ThreadLocalMap中会直接替换调这种不新鲜的entry。
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//触发一次Log2(N)复杂度的扫描,目的是清除过期Entry
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
Como el método de ThreadLocal get
:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//去ThreadLocalMap获取Entry,方法里面有key==null的清除逻辑
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//里面有key==null的清除逻辑
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// Entry的key为null,则表明没有外部引用,且被GC回收,是一个过期Entry
if (k == null)
expungeStaleEntry(i); //删除过期的Entry
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
5.2 La clave es una referencia débil, ¿el reciclaje de GC afectará el trabajo normal de ThreadLocal?
En este punto, algunos amigos pueden tener dudas, ya que es ThreadLocal
una referencia débil , ¿la GC la reciclará precipitadamente, lo que afectará el uso normal de la misma?key
key
ThreadLocal
- Referencias débiles : los objetos con referencias débiles tienen una vida útil más corta. Si un objeto solo tiene referencias débiles, el siguiente GC reciclará el objeto (independientemente de si el espacio de memoria actual es suficiente o no)
De hecho, no, porque ThreadLocal变量
está referenciado, no será reciclado por GC, a menos que se elimine manualmente ThreadLocal变量设置为null
, podemos ejecutar una demostración para verificarlo:
public class WeakReferenceTest {
public static void main(String[] args) {
Object object = new Object();
WeakReference<Object> testWeakReference = new WeakReference<>(object);
System.out.println("GC回收之前,弱引用:"+testWeakReference.get());
//触发系统垃圾回收
System.gc();
System.out.println("GC回收之后,弱引用:"+testWeakReference.get());
//手动设置为object对象为null
object=null;
System.gc();
System.out.println("对象object设置为null,GC回收之后,弱引用:"+testWeakReference.get());
}
}
运行结果:
GC回收之前,弱引用:java.lang.Object@7b23ec81
GC回收之后,弱引用:java.lang.Object@7b23ec81
对象object设置为null,GC回收之后,弱引用:null
La conclusión es, amigo, deja ir esta duda, jaja~
5.3 Demostración de pérdida de memoria ThreadLocal
Déjame mostrarte el siguiente ejemplo de una pérdida de memoria, de hecho, es usar el grupo de subprocesos para seguir poniendo objetos en él.
public class ThreadLocalTestDemo {
private static ThreadLocal<TianLuoClass> tianLuoThreadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
for (int i = 0; i < 10; ++i) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("创建对象:");
TianLuoClass tianLuoClass = new TianLuoClass();
tianLuoThreadLocal.set(tianLuoClass);
tianLuoClass = null; //将对象设置为 null,表示此对象不在使用了
// tianLuoThreadLocal.remove();
}
});
Thread.sleep(1000);
}
}
static class TianLuoClass {
// 100M
private byte[] bytes = new byte[100 * 1024 * 1024];
}
}
创建对象:
创建对象:
创建对象:
创建对象:
Exception in thread "pool-1-thread-4" java.lang.OutOfMemoryError: Java heap space
at com.example.dto.ThreadLocalTestDemo$TianLuoClass.<init>(ThreadLocalTestDemo.java:33)
at com.example.dto.ThreadLocalTestDemo$1.run(ThreadLocalTestDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
OOM apareció en el resultado de la ejecución, tianLuoThreadLocal.remove();
pero después de agregarlo, no lo hará OOM
.
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
......
复制代码
No configuramos manualmente la tianLuoThreadLocal
variable aquí null
, pero aún habrá una pérdida de memoria . Debido a que usamos el grupo de subprocesos, el grupo de subprocesos tiene un ciclo de vida largo, por lo que el grupo de subprocesos siempre tendrá el valor tianLuoClass
del objeto value
, incluso si la tianLuoClass = null;
referencia establecida todavía existe. Es como poner cada objeto object
en una list
lista y luego configurarlo por separado object
, null
la razón es la misma, el objeto de la lista todavía existe.
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
Object object = new Object();
list.add(object);
object = null;
System.out.println(list.size());
}
//运行结果
1
Así que la fuga de memoria ocurrió así, y finalmente la memoria se limitó, por lo que se descartó OOM
. Si lo agregamos threadLocal.remove();
, no habrá pérdida de memoria. ¿por qué? Debido a threadLocal.remove();
que se borrará Entry
, el código fuente es el siguiente:
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
//清除entry
e.clear();
expungeStaleEntry(i);
return;
}
}
}
Algunos amigos dijeron que, dado que las fugas de memoria no se deben necesariamente a referencias débiles, ¿por qué deben diseñarse como referencias débiles? Vamos a explorar:
6. ¿Por qué debe diseñarse la Clave de entrada como una referencia débil?
A través del código fuente, podemos ver Entry
que Key
está diseñado como una referencia débil ( la referencia débil utilizada ThreadLocalMap
) . ¿Por qué está diseñado como una referencia débil?ThreadLocal
Key
Recordemos primero las cuatro referencias:
- Referencia fuerte : por lo general,
new
sabemos que un objeto es una referencia fuerte. Por ejemplo,Object obj = new Object();
incluso en el caso de memoria insuficiente, la JVM preferiría arrojar un error OutOfMemory que reciclar este objeto. - Referencias blandas : si un objeto solo tiene referencias blandas, el espacio de memoria es suficiente y el recolector de basura no lo reclamará; si el espacio de memoria es insuficiente, se reclamará la memoria de estos objetos.
- Referencias débiles : los objetos con referencias débiles tienen una vida útil más corta. Si solo existen referencias débiles para un objeto, el siguiente GC reciclará el objeto (independientemente de si el espacio de memoria actual es suficiente o no).
- Referencias fantasma : si un objeto contiene solo referencias fantasma, es como si no tuviera referencias y el recolector de basura puede reclamarlo en cualquier momento. Las referencias fantasma se utilizan principalmente para realizar un seguimiento de la actividad de los objetos reclamados por el recolector de elementos no utilizados.
Primero echemos un vistazo a la documentación oficial, ¿por qué debería diseñarse como una referencia débil?
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
Permítanme mover el diagrama de referencia de ThreadLocal:
Analicemos por situación:
- Si
Key
usa referencias seguras: cuandoThreadLocal
el objeto se recicla, peroThreadLocalMap
aún tieneThreadLocal
una referencia segura, si no se elimina manualmente, ThreadLocal no se reciclará y se producirá el problema de pérdida de memoria de Entry. - Si
Key
usa referencias débiles: cuandoThreadLocal
el objeto se recicla, debido aThreadLocalMap
que contiene una referencia débil a ThreadLocal, ThreadLocal se reciclará incluso si no se elimina manualmente. Se borrarávalue
en la próximaThreadLocalMap
llamada .set,get,remove
Entry
Por lo tanto, se puede encontrar que el uso de referencias débiles Key
puede proporcionar una capa adicional de protección: las referencias débiles ThreadLocal
no perderán memoria fácilmente y las correspondientes se borrarán value
en la siguiente ThreadLocalMap
llamada .set,get,remove
De hecho, la causa raíz de nuestra pérdida de memoria fue que la memoria que ya no se usaba Entry
no se eliminó del subproceso ThreadLocalMap
. Entry
Generalmente, hay dos formas de eliminar los que ya no se usan :
- Una es, después de usarlo
ThreadLocal
, llamarlo manualmenteremove()
yEntry从ThreadLocalMap
eliminarlo - Otra forma es:
ThreadLocalMap
el mecanismo de compensación automática para borrar la caducidadEntry
(ThreadLocalMap
cadaget(),set()
vez que se activeEntry
la compensación de la caducidad)
7. InheritableThreadLocal garantiza datos compartidos entre subprocesos principales y secundarios
Sabemos ThreadLocal
que los subprocesos están aislados, si queremos que los subprocesos principales y secundarios compartan datos, ¿cómo hacerlo? InheritableThreadLocal
se puede utilizar Echemos un vistazo primero demo
:
public class InheritableThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
threadLocal.set("你好,七度");
inheritableThreadLocal.set("你好,七度");
Thread thread = new Thread(()->{
System.out.println("ThreadLocal value " + threadLocal.get());
System.out.println("InheritableThreadLocal value " + inheritableThreadLocal.get());
});
thread.start();
}
}
// 运行结果
ThreadLocal value null
InheritableThreadLocal value 你好,七度
Se puede encontrar que en el subproceso secundario, se puede obtener el valor de InheritableThreadLocal
la variable de tipo , pero ThreadLocal
no se puede obtener el valor de la variable de tipo.
Podemos entender que el valor del tipo no se puede obtener ThreadLocal
, porque está aislado por subprocesos. InheritableThreadLocal
¿Cómo se hace? ¿Cuál es el principio?
En Thread
una clase, además de las variables miembro threadLocals
, existe otra variable miembro: inheritableThreadLocals
. Estos dos tipos son iguales:
public class Thread implements Runnable {
ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;
}
Thread
En el método de la clase init
, hay una sección de configuración de inicialización:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
Se puede encontrar que cuando parent的inheritableThreadLocals
no está null
hecho , será parent
asignado inheritableThreadLocals
al hilo anterior inheritableThreadLocals
. Para decirlo sin rodeos, si el subproceso actual inheritableThreadLocals
no hace nada null
, copie uno del subproceso principal, similar al otro ThreadLocal
, pero los datos provienen del subproceso principal. Los amigos interesados pueden estudiar el código fuente ~
8. Escenarios de aplicación y notas para usar ThreadLocal
ThreadLocal
Un punto muy importante a tener en cuenta es que debe llamarse manualmente después de su uso remove()
.
Los ThreadLocal
escenarios de aplicación incluyen principalmente lo siguiente:
- Use la clase de herramienta de fecha, cuando se use
SimpleDateFormat
, use ThreadLocal para garantizar la seguridad lineal - Almacenar globalmente la información del usuario (la información del usuario se almacena
ThreadLocal
, por lo que el subproceso actual puede usarla en cualquier lugar que la necesite) - Garantice el mismo hilo, la conexión de la base de datos obtenida
Connection
es la misma, utilícelaThreadLocal
para resolver el problema de la seguridad del hilo - Úselo
MDC
para guardar información de registro.