Isoler les données entre les threads
Évitez de passer des paramètres pour chaque méthode dans le thread. Toutes les méthodes du thread peuvent obtenir directement les objets gérés dans ThreadLocal
. ThreadLocal
中管理的对象。
package com.example.test1.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class AsyncTest { // 使用threadlocal管理 private static final ThreadLocal<SimpleDateFormat> dateFormatLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); // 不用threadlocal进行管理,用于对比 SimpleDateFormat dateFormat = new SimpleDateFormat(); // 线程名称以task开头 @Async("taskExecutor") public void formatDateSync(String format, Date date) throws InterruptedException { SimpleDateFormat simpleDateFormat = dateFormatLocal.get(); simpleDateFormat.applyPattern(format); // 所有方法都可以直接使用这个变量,而不用根据形参传入 doSomething(); Thread.sleep(1000); System.out.println("sync " + Thread.currentThread().getName() + " | " + simpleDateFormat.format(date)); // 线程执行完毕,清除数据 dateFormatLocal.remove(); } // 线程名称以task2开头 @Async("taskExecutor2") public void formatDate(String format, Date date) throws InterruptedException { dateFormat.applyPattern(format); Thread.sleep(1000); System.out.println("normal " + Thread.currentThread().getName() + " | " + dateFormat.format(date)); } }
使用junit
进行测试:
@Test void test2() throws InterruptedException { for(int index = 1; index <= 10; ++index){ String format = index + "-yyyy-MM-dd"; Date time = new Date(); asyncTest.formatDate(format, time); } for(int index = 1; index <= 10; ++index){ String format = index + "-yyyy-MM-dd"; Date time = new Date(); asyncTest.formatDateSync(format, time); } }
结果如下,可以看到没有被 ThreadLocal
管理的变量已经无法匹配正确的format。
sync task--10 | 10-2023-04-11
sync task--9 | 9-2023-04-11
normal task2-3 | 2-2023-04-11
normal task2-5 | 2-2023-04-11
normal task2-10 | 2-2023-04-11
normal task2-6 | 2-2023-04-11
sync task--1 | 1-2023-04-11
normal task2-7 | 2-2023-04-11
normal task2-8 | 2-2023-04-11
normal task2-9 | 2-2023-04-11
sync task--6 | 6-2023-04-11
sync task--3 | 3-2023-04-11
sync task--2 | 2-2023-04-11
sync task--7 | 7-2023-04-11
sync task--4 | 4-2023-04-11
sync task--8 | 8-2023-04-11
normal task2-4 | 2-2023-04-11
normal task2-1 | 2-2023-04-11
sync task--5 | 5-2023-04-11
normal task2-2 | 2-2023-04-11
从ThreadLocal
中获取数据的过程:
先获取对应的线程。
通过 getMap(t)
拿到线程中的 ThreadLocalMap
ThreadLocalMap
是一个重新实现的散列表,基于两个元素实现散列:
用户定义的ThreadLocal
对象,例如:dateFormatLocal
。
封装了value
的Entry
对象。
通过map.getEntry(this)
方法,根据当前的 threadlocal
对象在散列表中获得对应的Entry
如果是第一次使用get()
, 则使用 setInitialValue()
调用用户重写的initialValue()
方法创建map并使用用户指定的值初始化。
在这种设计方式下,线程死去的时候,线程共享变量ThreadLocalMap
会被销毁。
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(); }
注意 Entry
对象是弱引用:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; // k: ThreadLocal, v: value Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
弱引用的常见用法是:
WeakReference<RoleDTO> weakReference = new WeakReference<>(new RoleDTO());
因此,在Entry
中,k
代表ThreadLocal
对象,它是弱引用。v代表ThreadLocal
管理的那个value
,是强引用。
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。随着垃圾回收器活动的增加以及内存占用的不断增加,程序性能会逐渐表现出来下降,极端情况下,会引发OutOfMemoryError
导致程序崩溃。
内存泄漏问题主要在线程池中出现,因为线程池中的线程是不断执行的,从任务队列中不断获取新的任务执行。但是任务中可能有ThreadLocal
对象,这些对象的ThreadLocal
会保存在线程的ThreadLocalMap
中,因此ThreadLocalMap
会越来越大。
但是ThreadLocal
是由任务(worker)传入的,一个任务执行结束后,对应的ThreadLocal
对象会被销毁。线程中的关系是: Thread -> ThreadLoalMap -> Entry8cea09e86e6da166e71a296f2b1f24bd
。ThreadLocal
由于是弱引用会,在GC的时候会被销毁,这会导致 ThreadLoalMap
中存在Entry5e33282b25ec20a016ad69d03248472f
。
使用remove()
由于线程池中的线程一直在运行,如果不对ThreadLoalMap
进行清理,那Entry5e33282b25ec20a016ad69d03248472f
会一直占用内存。remove()
方法会清除key==null
的Entry
。
使用static修饰
将ThreadLocal
设置成static
可以避免一个线程类多次传入线程池后重复创建Entry
。例如,有一个用户定义的线程
public class Test implements Runnable{ private static ThreadLocal<Integer> local = new ThreadLocal<>(); @Override public void run() { // do something } }
使用线程池处理10个任务。那么线程池中每个用来处理任务的线程的Thread.ThreadLocalMap
中都会保存一个Entry70cecb753554e47d1300fe45bef564fc
,由于添加了static
关键字,所有每个线程中的Entry
中的local
变量引用的都是同一个变量。这时就算发生内存泄漏,所有的Test类也只有一个local
@Test void contextLoads() { Runnable runnable = () -> { System.out.println(Thread.currentThread().getName()); }; for(int index = 1; index <= 10; ++index){ taskExecutor2.submit(new com.example.test1.service.Test()); } }🎜Utilisez
junit
pour tester : 🎜rrreee🎜Les résultats sont les suivants. Vous pouvez voir que les variables qui ne sont pas gérées par ThreadLocal
ne peuvent pas correspondre au. format correct. 🎜🎜tâche de synchronisation--10 | 10-2023-04-11🎜Principe de mise en œuvre🎜🎜De
tâche de synchronisation--9 | 9-2023-04-11
tâche normale2-3 | 11
tâche normale2-5 | 2-2023-04-11
tâche normale2-10 | 2-2023-04-11
tâche normale2-6 | br/>tâche de synchronisation--1 | 11/04/2023
tâche normale2-7 | 11/04/2023
tâche normale2-8/11/04/2023
tâche normale2-9 | 2-2023-04-11
tâche de synchronisation--6 | 6-2023-04-11
tâche de synchronisation--3 | />tâche de synchronisation--2 | 2-2023-04-11
tâche de synchronisation--7 | 7-2023-04-11
tâche de synchronisation--4 | br/>tâche de synchronisation--8 | 8-2023-04-11
tâche normale2-4 | 2-2023-04-11
tâche normale2-1 |2023-04-11
tâche de synchronisation--5 | 5-2023-04-11
tâche normale2-2 | 2-2023-04-11🎜
ThreadLocal
Le processus d'obtention des données : 🎜🎜Obtenez d'abord le fil de discussion correspondant. 🎜🎜Obtenez le ThreadLocalMap
dans le fil via getMap(t)
🎜🎜ThreadLocalMap
est une table de hachage réimplémentée, implémentée sur la base de deux éléments Hachage : 🎜🎜🎜🎜Objet ThreadLocal
défini par l'utilisateur, par exemple : dateFormatLocal
. 🎜🎜🎜🎜L'objet Entry
qui encapsule value
. 🎜🎜🎜Utilisez la méthode map.getEntry(this)
pour obtenir l'Entry
correspondante dans la table de hachage en fonction du threadlocal
actuel > objet 🎜🎜Si c'est la première fois que vous utilisez get()
, utilisez setInitialValue()
pour appeler le initialValue()
remplacé par l'utilisateur. méthode pour créer une carte et initialisée avec les valeurs spécifiées par l'utilisateur. 🎜🎜Dans cette conception, lorsque le thread meurt, la variable partagée du thread ThreadLocalMap
sera détruite. 🎜rrreee🎜Notez que l'objet Entry
est une référence faible : 🎜rrreee🎜L'usage courant de la référence faible est : 🎜rrreee🎜Par conséquent, dans Entry
, k
représente l'objet ThreadLocal
, qui est une référence faible. v représente la valeur
gérée par ThreadLocal
, qui est une référence forte. 🎜OutOfMemoryError
sera déclenché, provoquant le blocage du programme. 🎜🎜Les problèmes de fuite de mémoire se produisent principalement dans le pool de threads, car les threads du pool de threads sont exécutés en continu et de nouvelles tâches sont continuellement obtenues à partir de la file d'attente des tâches pour exécution. Cependant, il peut y avoir des objets ThreadLocal
dans la tâche, et le ThreadLocal
de ces objets sera enregistré dans le ThreadLocalMap
du thread, donc ThreadLocalMap
Il deviendra de plus en plus grand. 🎜🎜Mais ThreadLocal
est transmis par la tâche (worker). Après l'exécution d'une tâche, l'objet ThreadLocal
correspondant sera détruit. La relation dans le fil est la suivante : Thread -> ThreadLoalMap -> Entry8cea09e86e6da166e71a296f2b1f24bd
. ThreadLocal
sera détruit lors du GC car il s'agit d'une référence faible, ce qui fera que Entry5e33282b25ec20a016ad69d03248472f
existera dans ThreadLoalMap
. 🎜🎜Utilisez Remove()🎜🎜Étant donné que les threads du pool de threads sont toujours en cours d'exécution, si ThreadLoalMap
n'est pas nettoyé, alors Entry5e33282b25ec20a016ad69d03248472f
occupera toujours de la mémoire. La méthode remove()
effacera l'Entrée
de key==null
. 🎜🎜Utilisez la modification statique🎜🎜Définissez ThreadLocal
sur static
pour éviter la création répétée de après qu'une classe de thread soit passée dans le pool de threads plusieurs fois Entrée
. Par exemple, il existe un thread défini par l'utilisateur 🎜rrreee🎜 qui utilise un pool de threads pour gérer 10 tâches. Ensuite, le Thread.ThreadLocalMap
de chaque thread utilisé pour traiter les tâches dans le pool de threads enregistrera un Entry70cecb753554e47d1300fe45bef564fc
, en raison de l'ajout de static code> mot-clé, toutes les variables <code>local
dans Entry
dans chaque thread font référence à la même variable. Même si une fuite de mémoire se produit à ce moment-là, toutes les classes Test n'auront qu'un seul objet local
, ce qui n'entraînera pas une utilisation excessive de la mémoire. 🎜rrreeeCe qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!