Heim  >  Artikel  >  Java  >  Was ist die Verwendung und das Prinzip von ThreadLocal in Java?

Was ist die Verwendung und das Prinzip von ThreadLocal in Java?

王林
王林nach vorne
2023-04-13 17:31:121078Durchsuche

Verwendung

  • Isolieren Sie Daten zwischen Threads.

  • Vermeiden Sie die Übergabe von Parametern für jede Methode im Thread. Alle Methoden im Thread können die in ThreadLocal verwalteten Objekte direkt abrufen. 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

  • 封装了valueEntry对象。

通过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 -> Entry8cea09e86e6da166e71a296f2b1f24bdThreadLocal由于是弱引用会,在GC的时候会被销毁,这会导致 ThreadLoalMap中存在Entry5e33282b25ec20a016ad69d03248472f

使用remove()

由于线程池中的线程一直在运行,如果不对ThreadLoalMap进行清理,那Entry5e33282b25ec20a016ad69d03248472f会一直占用内存。remove()方法会清除key==nullEntry

使用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());
   }
}
🎜Verwenden Sie junit zum Testen: 🎜rrreee🎜Die Ergebnisse sind wie folgt: Sie können sehen, dass Variablen, die nicht von ThreadLocal verwaltet werden, nicht übereinstimmen können richtiges Format. 🎜
🎜sync task--10 |. 10-2023-04-11
sync task--9 |.normal task2-3 |. 11
normale Aufgabe2-5 |. 2-2023-04-11
normale Aufgabe2-10 |2-2023-04-11
normale Aufgabe2-6 | br/>Synchronisierungsaufgabe--1 |. 1-2023-04-11
normale Aufgabe2-7 | 2-2023-04-11
normale Aufgabe2-8 | />normale Aufgabe2-9 |. 2023-04-11
Synchronisierungsaufgabe--6 | 6-2023-04-11
Synchronisierungsaufgabe--3 | />Synchronisierungsaufgabe--2 |. 2023-04-11
Synchronisierungsaufgabe--7 | 7-2023-04-11
Synchronisierungsaufgabe--4 | br/>Synchronisierungsaufgabe--8 |. 8-2023-04-11
normale Aufgabe2-4 | 2-2023-04-11
normale Aufgabe2-1 | />Synchronisierungsaufgabe--5 |. 5-2023-04-11
normale Aufgabe2-2 |. 2-2023-04-11🎜
🎜Implementierungsprinzip🎜🎜Von ThreadLocal Der Prozess zum Abrufen von Daten: 🎜🎜Erstens den entsprechenden Thread abrufen. 🎜🎜Holen Sie sich die ThreadLocalMap im Thread über getMap(t)🎜🎜ThreadLocalMap ist eine neu implementierte Hash-Tabelle, die auf der Grundlage von zwei Elementen implementiert wird Hash: 🎜🎜🎜🎜Benutzerdefiniertes ThreadLocal-Objekt, zum Beispiel: dateFormatLocal. 🎜🎜🎜🎜Das Entry-Objekt, das value kapselt. 🎜🎜🎜Verwenden Sie die Methode map.getEntry(this), um den entsprechenden Eintrag in der Hash-Tabelle basierend auf dem aktuellen threadlocalzu erhalten > Objekt 🎜🎜Wenn Sie get() zum ersten Mal verwenden, verwenden Sie setInitialValue(), um den vom Benutzer überschriebenen initialValue() aufzurufen Methode zum Erstellen einer Karte und Initialisieren mit benutzerdefinierten Werten. 🎜🎜Bei diesem Design wird die gemeinsam genutzte Thread-Variable ThreadLocalMap zerstört, wenn der Thread stirbt. 🎜rrreee🎜Beachten Sie, dass das Entry-Objekt eine schwache Referenz ist: 🎜rrreee🎜Die übliche Verwendung einer schwachen Referenz ist: 🎜rrreee🎜Daher ist in Entry k stellt das ThreadLocal-Objekt dar, das eine schwache Referenz ist. v stellt den von ThreadLocal verwalteten Wert dar, der eine starke Referenz darstellt. 🎜

Memory Leak

🎜Memory Leak bedeutet, dass nutzlose Objekte (Objekte, die nicht mehr verwendet werden) weiterhin Speicher belegen oder der Speicher nutzloser Objekte nicht rechtzeitig freigegeben werden kann, was dazu führt Ein Verlust von Speicherplatz wird als Speicherleck bezeichnet. Wenn die Aktivität des Garbage Collectors zunimmt und die Speichernutzung weiter zunimmt, nimmt die Programmleistung allmählich ab. In extremen Fällen wird OutOfMemoryError ausgelöst, was zum Absturz des Programms führt. 🎜🎜Speicherverlustprobleme treten hauptsächlich im Thread-Pool auf, da die Threads im Thread-Pool kontinuierlich ausgeführt werden und ständig neue Aufgaben zur Ausführung aus der Aufgabenwarteschlange abgerufen werden. Es können jedoch ThreadLocal-Objekte in der Aufgabe vorhanden sein, und der ThreadLocal dieser Objekte wird in der ThreadLocalMap des Threads gespeichert, also ThreadLocalMap Es wird immer größer. 🎜🎜Aber ThreadLocal wird von der Aufgabe (Worker) übergeben. Nachdem eine Aufgabe ausgeführt wurde, wird das entsprechende ThreadLocal-Objekt zerstört. Die Beziehung im Thread lautet: Thread -> ThreadLoalMap -> Entry8cea09e86e6da166e71a296f2b1f24bd. ThreadLocal wird während der GC zerstört, da es sich um eine schwache Referenz handelt, die dazu führt, dass Entry5e33282b25ec20a016ad69d03248472f in ThreadLoalMap vorhanden ist. 🎜🎜Verwenden Sie „remove()“🎜🎜Da die Threads im Thread-Pool immer ausgeführt werden und ThreadLoalMap nicht bereinigt wird, wird Entry5e33282b25ec20a016ad69d03248472f code> wird immer Speicher belegen. Die Methode <code>remove() löscht den Eintrag von key==null. 🎜🎜Verwenden Sie statische Modifikation🎜🎜Setzen Sie ThreadLocal auf statisch, um eine wiederholte Erstellung von zu vermeiden, nachdem eine Thread-Klasse an den Thread-Pool übergeben wurde mehrfacher Eintrag. Beispielsweise gibt es einen benutzerdefinierten Thread 🎜rrreee🎜, der einen Thread-Pool zur Bearbeitung von 10 Aufgaben verwendet. Dann speichert die Thread.ThreadLocalMap jedes Threads, der zum Verarbeiten von Aufgaben im Thread-Pool verwendet wird, aufgrund der Hinzufügung von staticEintrag70cecb753554e47d1300fe45bef564fc /code> code>-Schlüsselwort, alle lokalen-Variablen in Entry in jedem Thread beziehen sich auf dieselbe Variable. Selbst wenn zu diesem Zeitpunkt ein Speicherverlust auftritt, verfügen alle Testklassen nur über ein lokales-Objekt, was nicht zu einer übermäßigen Speichernutzung führt. 🎜rrreee

Das obige ist der detaillierte Inhalt vonWas ist die Verwendung und das Prinzip von ThreadLocal in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen