>Java >java지도 시간 >Java에서 ThreadLocal의 사용법과 원리는 무엇입니까?

Java에서 ThreadLocal의 사용법과 원리는 무엇입니까?

王林
王林앞으로
2023-04-13 17:31:121091검색

사용법

  • 스레드 간 데이터 격리

  • 스레드의 각 메서드에 대한 매개변수 전달을 피하세요. 스레드의 모든 메서드는 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

  • 封装了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());
   }
}
🎜junit를 사용하여 테스트해 보세요. 🎜rrreee🎜결과는 다음과 같습니다. ThreadLocal에서 관리하지 않는 변수는 일치할 수 없습니다. 올바른 형식. 🎜
🎜동기화 작업--10 | 10-2023-04-11
동기화 작업--9 | 9-2023-04-11
정상 작업2-3 | 11
정상 태스크2-5 | 2-2023-04-11
정상 태스크2-10 | 2-2023-04-11
정상 태스크2-6동기화 작업--1 | 1-2023-04-11
일반 작업2-7 | 2-2023-04-11
일반 작업2-8
일반 작업2-9 | 2-2023-04-11
동기화 작업--6 | 6-2023-04-11
동기화 작업--3
동기화 작업--2 | 2-2023-04-11
동기화 작업--7 | 7-2023-04-11
동기화 작업--4동기화 작업--8 | 8-2023-04-11
정상 작업2-4 | 2-2023-04-11
정상 작업2-1
동기화 작업--5 | 5-2023-04-11
normal task2-2 | 2-2023-04-11🎜
🎜구현 원칙🎜🎜ThreadLocal 데이터를 얻는 과정: 🎜🎜먼저 해당 스레드를 얻습니다. 🎜🎜getMap(t)를 통해 스레드의 ThreadLocalMap을 가져옵니다.🎜🎜ThreadLocalMap은 두 요소를 기반으로 구현된 재구현된 해시 테이블입니다. 해시: 🎜🎜🎜🎜사용자 정의 ThreadLocal 개체(예: dateFormatLocal). 🎜🎜🎜🎜을 캡슐화하는 Entry 개체입니다. 🎜🎜🎜map.getEntry(this) 메소드를 사용하여 현재 threadlocal를 기반으로 해시 테이블에서 해당 Entry를 가져옵니다. > object 🎜🎜get()을 처음 사용하는 경우 setInitialValue()를 사용하여 사용자가 재정의한 initialValue()를 호출하세요. 지도를 생성하고 사용자가 지정한 값으로 초기화하는 메서드입니다. 🎜🎜이 설계에서는 스레드가 종료되면 스레드 공유 변수 ThreadLocalMap이 삭제됩니다. 🎜rrreee🎜Entry 개체는 약한 참조입니다. 🎜rrreee🎜약한 참조의 일반적인 사용법은 다음과 같습니다. 🎜rrreee🎜따라서 Entry에서 k 는 약한 참조인 ThreadLocal 개체를 나타냅니다. v는 강력한 참조인 ThreadLocal에서 관리하는 을 나타냅니다. 🎜

Memory Leak

🎜Memory Leak은 쓸모없는 객체(더 이상 사용되지 않는 객체)가 계속해서 메모리를 점유하거나 쓸모없는 객체의 메모리가 제때 해제되지 못해 결과적으로 메모리 공간의 손실을 메모리 누수라고 합니다. 가비지 수집기 활동이 증가하고 메모리 사용량이 계속 증가하면 프로그램 성능이 점차 저하되며 극단적인 경우에는 OutOfMemoryError가 발생하여 프로그램이 중단됩니다. 🎜🎜메모리 누수 문제는 주로 스레드 풀에서 발생하는데, 스레드 풀에 있는 스레드들은 지속적으로 실행되고, 작업 큐에서 새로운 작업을 지속적으로 얻어 실행되기 때문입니다. 그러나 작업에 ThreadLocal 개체가 있을 수 있으며 이러한 개체의 ThreadLocal은 스레드의 ThreadLocalMap에 저장되므로 ThreadLocalMap 점점 더 커질 것입니다. 🎜🎜그러나 ThreadLocal은 작업(작업자)에 의해 전달됩니다. 작업이 실행된 후 해당 ThreadLocal 개체가 삭제됩니다. 스레드의 관계는 Thread -> ThreadLoalMap -> Entry<threadlocal object></threadlocal>입니다. ThreadLocal은 약한 참조이기 때문에 GC 중에 제거됩니다. 이로 인해 Entry<null object></null>ThreadLoalMap에 존재하게 됩니다. 🎜🎜remove() 사용🎜🎜스레드 풀의 스레드는 항상 실행 중이므로 ThreadLoalMap이 정리되지 않으면 Entry5e33282b25ec20a016ad69d03248472f code>는 항상 메모리를 차지합니다. <code>remove() 메서드는 key==null항목을 지웁니다. 🎜🎜정적 수정 사용🎜🎜 ThreadLocalstatic으로 설정하여 스레드 풀에 여러 번 전달한 후 스레드 클래스가 반복적으로 생성되는 것을 방지하세요. 항목. 예를 들어 스레드 풀을 사용하여 10개의 작업을 처리하는 사용자 정의 스레드 🎜rrreee🎜가 있습니다. 그런 다음 스레드 풀에서 작업을 처리하는 데 사용되는 각 스레드의 Thread.ThreadLocalMapstaticEntry<local integer></local>를 저장합니다. /code> code> 키워드, 각 스레드의 Entry에 있는 모든 local 변수는 동일한 변수를 참조합니다. 이때 메모리 누수가 발생하더라도 모든 Test 클래스에는 local 객체가 하나만 있으므로 과도한 메모리 사용이 발생하지 않습니다. 🎜아아아아

위 내용은 Java에서 ThreadLocal의 사용법과 원리는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제