欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中ThreadLocal的用法和原理詳解

 更新時(shí)間:2023年04月12日 14:07:43   作者:twilight0402  
這篇文章主要為大家詳細(xì)介紹了Java中ThreadLocal的用法和原理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下

用法

  • 隔離各個(gè)線程間的數(shù)據(jù)
  • 避免線程內(nèi)每個(gè)方法都進(jìn)行傳參,線程內(nèi)的所有方法都可以直接獲取到ThreadLocal中管理的對(duì)象。
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進(jìn)行管理,用于對(duì)比
    SimpleDateFormat dateFormat = new SimpleDateFormat();

    // 線程名稱以task開頭
    @Async("taskExecutor")
    public void formatDateSync(String format, Date date) throws InterruptedException {
        SimpleDateFormat simpleDateFormat = dateFormatLocal.get();
        simpleDateFormat.applyPattern(format);
        
        // 所有方法都可以直接使用這個(gè)變量,而不用根據(jù)形參傳入
        doSomething();
        
        Thread.sleep(1000);
        System.out.println("sync " + Thread.currentThread().getName() +  " | " + simpleDateFormat.format(date));
        
        // 線程執(zhí)行完畢,清除數(shù)據(jù)
        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進(jìn)行測(cè)試:

	@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);
		}
	}

結(jié)果如下,可以看到?jīng)]有被 ThreadLocal 管理的變量已經(jīng)無(wú)法匹配正確的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

實(shí)現(xiàn)原理

ThreadLocal中獲取數(shù)據(jù)的過(guò)程:

先獲取對(duì)應(yīng)的線程。

通過(guò) getMap(t)拿到線程中的 ThreadLocalMap

ThreadLocalMap 是一個(gè)重新實(shí)現(xiàn)的散列表,基于兩個(gè)元素實(shí)現(xiàn)散列:

  • 用戶定義的ThreadLocal對(duì)象,例如:dateFormatLocal。
  • 封裝了valueEntry對(duì)象。

通過(guò)map.getEntry(this)方法,根據(jù)當(dāng)前的 threadlocal對(duì)象在散列表中獲得對(duì)應(yīng)的Entry

如果是第一次使用get(), 則使用 setInitialValue()調(diào)用用戶重寫的initialValue()方法創(chuàng)建map并使用用戶指定的值初始化。

在這種設(shè)計(jì)方式下,線程死去的時(shí)候,線程共享變量ThreadLocalMap會(huì)被銷毀。

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對(duì)象是弱引用:

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;
    }
}

弱引用的常見(jiàn)用法是:

WeakReference<RoleDTO> weakReference = new WeakReference<>(new RoleDTO());

因此,在Entry中,k 代表ThreadLocal對(duì)象,它是弱引用。v代表ThreadLocal管理的那個(gè)value,是強(qiáng)引用。

內(nèi)存泄漏

內(nèi)存泄漏是指無(wú)用對(duì)象(不再使用的對(duì)象)持續(xù)占有內(nèi)存或無(wú)用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏。隨著垃圾回收器活動(dòng)的增加以及內(nèi)存占用的不斷增加,程序性能會(huì)逐漸表現(xiàn)出來(lái)下降,極端情況下,會(huì)引發(fā)OutOfMemoryError導(dǎo)致程序崩潰。

內(nèi)存泄漏問(wèn)題主要在線程池中出現(xiàn),因?yàn)榫€程池中的線程是不斷執(zhí)行的,從任務(wù)隊(duì)列中不斷獲取新的任務(wù)執(zhí)行。但是任務(wù)中可能有ThreadLocal對(duì)象,這些對(duì)象的ThreadLocal會(huì)保存在線程的ThreadLocalMap中,因此ThreadLocalMap會(huì)越來(lái)越大。

但是ThreadLocal是由任務(wù)(worker)傳入的,一個(gè)任務(wù)執(zhí)行結(jié)束后,對(duì)應(yīng)的ThreadLocal對(duì)象會(huì)被銷毀。線程中的關(guān)系是: Thread -> ThreadLoalMap -> Entry<ThreadLocal, Object>ThreadLocal由于是弱引用會(huì),在GC的時(shí)候會(huì)被銷毀,這會(huì)導(dǎo)致 ThreadLoalMap中存在Entry<null, Object>。

使用remove()

由于線程池中的線程一直在運(yùn)行,如果不對(duì)ThreadLoalMap進(jìn)行清理,那Entry<null, Object>會(huì)一直占用內(nèi)存。remove()方法會(huì)清除key==nullEntry。

使用static修飾

ThreadLocal設(shè)置成static可以避免一個(gè)線程類多次傳入線程池后重復(fù)創(chuàng)建Entry。例如,有一個(gè)用戶定義的線程

public class Test implements Runnable{
    private static ThreadLocal<Integer> local = new ThreadLocal<>();
    @Override
    public void run() {
        // do something
    }
}

使用線程池處理10個(gè)任務(wù)。那么線程池中每個(gè)用來(lái)處理任務(wù)的線程的Thread.ThreadLocalMap中都會(huì)保存一個(gè)Entry<local, Integer>,由于添加了static關(guān)鍵字,所有每個(gè)線程中的Entry中的local變量引用的都是同一個(gè)變量。這時(shí)就算發(fā)生內(nèi)存泄漏,所有的Test類也只有一個(gè)local對(duì)象,不會(huì)導(dǎo)致內(nèi)存占用過(guò)多。

@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());
   }
}

到此這篇關(guān)于Java中ThreadLocal的用法和原理詳解的文章就介紹到這了,更多相關(guān)Java ThreadLocal內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在IDEA中搭建最小可用SpringMVC項(xiàng)目(純Java配置)

    在IDEA中搭建最小可用SpringMVC項(xiàng)目(純Java配置)

    這篇文章主要介紹了在IDEA中搭建最小可用SpringMVC項(xiàng)目(純Java配置),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • MyBatis實(shí)現(xiàn)物理分頁(yè)的實(shí)例

    MyBatis實(shí)現(xiàn)物理分頁(yè)的實(shí)例

    這篇文章主要介紹了MyBatis實(shí)現(xiàn)物理分頁(yè)的實(shí)例,MyBatis使用RowBounds實(shí)現(xiàn)的分頁(yè)是邏輯分頁(yè),有興趣的可以了解一下。
    2017-01-01
  • IDEA實(shí)現(xiàn)JDBC的操作步驟

    IDEA實(shí)現(xiàn)JDBC的操作步驟

    JDBC提供了一種基準(zhǔn),據(jù)此可以構(gòu)建更高級(jí)的工具和接口,使數(shù)據(jù)庫(kù)開發(fā)人員能夠編寫數(shù)據(jù)庫(kù)應(yīng)用程序,本文給大家介紹IDEA實(shí)現(xiàn)JDBC的操作步驟,感興趣的朋友一起看看吧
    2022-01-01
  • Java集合遍歷實(shí)現(xiàn)方法及泛型通配

    Java集合遍歷實(shí)現(xiàn)方法及泛型通配

    這篇文章主要介紹了Java集合遍歷實(shí)現(xiàn)方法及泛型通配,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • java異常處理機(jī)制示例(java拋出異常、捕獲、斷言)

    java異常處理機(jī)制示例(java拋出異常、捕獲、斷言)

    這篇文章主要介紹了java異常處理機(jī)制示例(java拋出異常、捕獲、斷言),需要的朋友可以參考下
    2014-05-05
  • 使用springboot不自動(dòng)初始化數(shù)據(jù)庫(kù)連接池

    使用springboot不自動(dòng)初始化數(shù)據(jù)庫(kù)連接池

    這篇文章主要介紹了使用springboot不自動(dòng)初始化數(shù)據(jù)庫(kù)連接池,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 關(guān)于SpringBoot攔截器攔截靜態(tài)資源的問(wèn)題

    關(guān)于SpringBoot攔截器攔截靜態(tài)資源的問(wèn)題

    這篇文章主要介紹了關(guān)于SpringBoot攔截器攔截靜態(tài)資源的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Mybatis-Plus開發(fā)提速器mybatis-plus-generator-ui詳解

    Mybatis-Plus開發(fā)提速器mybatis-plus-generator-ui詳解

    這篇文章主要介紹了Mybatis-Plus開發(fā)提速器mybatis-plus-generator-ui,本文簡(jiǎn)要介紹一款基于Mybatis-Plus的代碼自助生成器,文章通過(guò)實(shí)例集成的方式來(lái)詳細(xì)講解mybatis-plus-generator-ui,從相關(guān)概念到實(shí)際集成案例,以及具體的擴(kuò)展開發(fā)介紹,需要的朋友可以參考下
    2022-11-11
  • ArrayList及HashMap的擴(kuò)容規(guī)則講解

    ArrayList及HashMap的擴(kuò)容規(guī)則講解

    今天小編就為大家分享一篇關(guān)于ArrayList及HashMap的擴(kuò)容規(guī)則講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-02-02
  • Mybatis延遲加載原理和延遲加載配置詳解

    Mybatis延遲加載原理和延遲加載配置詳解

    這篇文章主要介紹了Mybatis延遲加載原理和延遲加載配置詳解,MyBatis中的延遲加載,也稱為懶加載,是指在進(jìn)行表的關(guān)聯(lián)查詢時(shí),按照設(shè)置延遲規(guī)則推遲對(duì)關(guān)聯(lián)對(duì)象的select查詢,需要的朋友可以參考下
    2023-10-10

最新評(píng)論