电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Java中ThreadLocal的一些理解

瀏覽:52日期:2022-08-19 17:38:29

前言

面試的時(shí)候被問(wèn)到ThreadLocal的相關(guān)知識(shí),沒(méi)有回答好(奶奶的,現(xiàn)在感覺(jué)問(wèn)啥都能被問(wèn)倒),所以我決定先解決這幾次面試中都遇到的高頻問(wèn)題,把這幾個(gè)硬骨頭都能理解的透徹的說(shuō)出來(lái)了,感覺(jué)最起碼不能總是一輪游。

ThreadLocal介紹

ThreadLocal是JDK1.2開(kāi)始就提供的一個(gè)用來(lái)存儲(chǔ)線程本地變量的類。ThreadLocal中的變量是在每個(gè)線程中獨(dú)立存在的,當(dāng)多個(gè)線程訪問(wèn)ThreadLocal中的變量的時(shí)候,其實(shí)都是訪問(wèn)的自己當(dāng)前線程的內(nèi)存中的變量,從而保證的變量的線程安全。

我們一般在使用ThreadLocal的時(shí)候都是為了解決線程中存在的變量競(jìng)爭(zhēng)問(wèn)題。其實(shí)解決這類問(wèn)題,通常大家也會(huì)想到使用synchronized來(lái)加鎖解決。

例如在解決SimpleDateFormat的線程安全的時(shí)候。SimpleDateFormat是非線程安全的,它里面無(wú)論的是format()方法還是parse()方法,都有使用它自己內(nèi)部的一個(gè)Calendar類的對(duì)象,format方法是設(shè)置時(shí)間,parse()方法里面是先調(diào)用Calendar的clear()方法,然后又調(diào)用了Calendar的set()方法(賦值),如果一個(gè)線程剛調(diào)用了set()進(jìn)行賦值,這個(gè)時(shí)候又來(lái)了一個(gè)線程直接調(diào)用了clear()方法,那么這個(gè)parse()方法執(zhí)行的結(jié)果就會(huì)有問(wèn)題的。

解決辦法一將使用SimpleDateformat的方法加上synchronized,這樣雖然保證了線程安全,但卻降低了效率,同一時(shí)間只有一個(gè)線程能使用格式化時(shí)間的方法。

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss');public static synchronized String formatDate(Date date){ return simpleDateFormat.format(date);}

解決辦法二將SimpleDateFormat的對(duì)象,放到ThreadLocal里面,這樣每個(gè)線程中都有一個(gè)自己的格式對(duì)象的副本了。互不干擾,從而保證了線程安全。

private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat('yyyy-MM-dd HH:mm:ss'));public static String formatDate(Date date){ return simpleDateFormatThreadLocal.get().format(date);}

ThreadLocal的原理

我們先看一下ThreadLocal是怎么使用的。

ThreadLocal<Integer> threadLocal99 = new ThreadLocal<Integer>();threadLocal99.set(3);int num = threadLocal99.get();System.out.println('數(shù)字:'+num);threadLocal99.remove();System.out.println('數(shù)字Empty:'+threadLocal99.get());

運(yùn)行結(jié)果:

數(shù)字:3數(shù)字Empty:null

使用起來(lái)很簡(jiǎn)單,主要是將變量放到ThreadLocal里面,在線程執(zhí)行過(guò)程中就可以取到,當(dāng)執(zhí)行完成后在remove掉就可以了,只要沒(méi)有調(diào)用remove()當(dāng)前線程在執(zhí)行過(guò)程中都是可以拿到變量數(shù)據(jù)的。因?yàn)槭欠诺搅水?dāng)前執(zhí)行的線程中,所以ThreadLocal中的變量值只能當(dāng)前線程來(lái)使用,從而保證的了線程安全(當(dāng)前線程的子線程其實(shí)也是可以獲取到的)。

來(lái)看一下ThreadLocal的set()方法源碼

public void set(T value) { // 獲取當(dāng)前線程 Thread t = Thread.currentThread(); // 獲取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); // ThreadLocalMap 對(duì)象是否為空,不為空則直接將數(shù)據(jù)放入到ThreadLocalMap中 if (map != null) map.set(this, value); else createMap(t, value); // ThreadLocalMap對(duì)象為空,則先創(chuàng)建對(duì)象,再賦值。}

我們看到變量都是存放在了ThreadLocalMap這個(gè)變量中的。那么ThreadLocalMap又是怎么來(lái)的呢?

ThreadLocalMap getMap(Thread t) { return t.threadLocals;}

public class Thread implements Runnable {... .../* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... ...}

通過(guò)上面的源碼,我們發(fā)現(xiàn)ThreadLocalMap變量是當(dāng)前執(zhí)行線程中的一個(gè)變量,所以說(shuō),ThreadLocal中存放的數(shù)據(jù)其實(shí)都是放到了當(dāng)前執(zhí)行線程中的一個(gè)變量里面了。也就是存儲(chǔ)在了當(dāng)前的線程對(duì)象里了,別的線程里面是另一個(gè)線程對(duì)象了,拿不到其他線程對(duì)象中的數(shù)據(jù),所以數(shù)據(jù)自然就隔離開(kāi)了。

那么ThreadLocalMap是怎么存儲(chǔ)數(shù)據(jù)的呢?ThreadLocalMap 是ThreadLocal類里的一個(gè)內(nèi)部類,雖然類的名字上帶著Map但卻沒(méi)有實(shí)現(xiàn)Map接口,只是結(jié)構(gòu)和Map類似而已。

Java中ThreadLocal的一些理解

ThreadLocalMap內(nèi)部其實(shí)是一個(gè)Entry數(shù)組,Entry是ThreadLocalMap中的一個(gè)內(nèi)部類,繼承自WeakReference,并將ThreadLocal類型的對(duì)象設(shè)置為了Entry的Key,以及對(duì)Key設(shè)置成弱引用。ThreadLocalMap的內(nèi)部數(shù)據(jù)結(jié)構(gòu),就大概是這樣的key,value組成的Entry的數(shù)組集合。

Java中ThreadLocal的一些理解

和真正的Map還是有區(qū)別的,沒(méi)有鏈表了,這樣在解決key的hash沖突的時(shí)候措施肯定就和HashMap不一樣了。一個(gè)線程中是可以創(chuàng)建多個(gè)ThreadLocal對(duì)象的,多個(gè)ThreadLocal對(duì)象就會(huì)存放多個(gè)數(shù)據(jù),那么在ThreadLocalMap中就會(huì)以數(shù)組的形式存放這些數(shù)據(jù)。我們來(lái)看一下具體的ThreadLocalMap的set()方法的源碼

/** * Set the value associated with key. * @param key the thread local object * @param value the value to be set */private void set(ThreadLocal<?> key, Object value) { // We don’t use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; // 定位在數(shù)組中的位置 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // 如果當(dāng)前位置不為空,并且當(dāng)前位置的key和傳過(guò)來(lái)的key相等,那么就會(huì)覆蓋當(dāng)前位置的數(shù)據(jù) if (k == key) { e.value = value; return; } // 如果當(dāng)前位置為空,則初始化一個(gè)Entry對(duì)象,放到當(dāng)前位置。 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 如果當(dāng)前位置不為空,并且當(dāng)前位置的key也不等于要賦值的key ,那么將去找下一個(gè)空位置,直接將數(shù)據(jù)放到下一個(gè)空位置處。 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();}

我們從set()方法中可以看到,處理邏輯有四步。

第一步先根據(jù)Threadlocal對(duì)象的hashcode和數(shù)組長(zhǎng)度做與運(yùn)算獲取數(shù)據(jù)應(yīng)該放在當(dāng)前數(shù)組中的位置。 第二步就是判斷當(dāng)前位置是否為空,為空的話就直接初始化一個(gè)Entry對(duì)象,放到當(dāng)前位置。 第三步如果當(dāng)前位置不為空,而當(dāng)前位置的Entry中的key和傳過(guò)來(lái)的key一樣,那么直接覆蓋掉當(dāng)前位置的數(shù)據(jù)。 第四步如果當(dāng)前位置不為空,并且當(dāng)前位置的Entry中的key和傳過(guò)來(lái)的key 也不一樣,那么就會(huì)去找下一個(gè)空位置,然后將數(shù)據(jù)存放到空位置(數(shù)組超過(guò)長(zhǎng)度后,會(huì)執(zhí)行擴(kuò)容的);

在get的時(shí)候也是類似的邏輯,先通過(guò)傳入的ThreadLocal的hashcode獲取在Entry數(shù)組中的位置,然后拿當(dāng)前位置的Entry的Key和傳入的ThreadLocal對(duì)比,相等的話,直接把數(shù)據(jù)返回,如果不相等就去判斷和數(shù)組中的下一個(gè)值的key是否相等。。。

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 return getEntryAfterMiss(key, i, e);}

/** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key’s hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */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; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null;}

我們上文一直說(shuō),ThreadLocal是保存在單個(gè)線程中的數(shù)據(jù),每個(gè)線程都有自己的數(shù)據(jù),但是實(shí)際ThreadLocal里面的真正的對(duì)象數(shù)據(jù),其實(shí)是保存在堆里面的,而線程里面只是存儲(chǔ)了對(duì)象的引用而已。并且我們?cè)谑褂玫臅r(shí)候通常需要在上一個(gè)線程執(zhí)行的方法的上下文共享ThreadLocal中的變量。例如我的主線程是在某個(gè)方法中執(zhí)行代碼呢,但是這個(gè)方法中有一段代碼時(shí)新創(chuàng)建了一個(gè)線程,在這個(gè)線程里面還使用了我這個(gè)正在執(zhí)行的方法里面的定義的ThreadLocal里面的變量。這個(gè)時(shí)候,就是需要從新線程里面調(diào)用外面線程的數(shù)據(jù),這個(gè)就需要線程間共享了。這種子父線程共享數(shù)據(jù)的情況,ThreadLocal也是支持的。例如:

ThreadLocal threadLocalMain = new InheritableThreadLocal(); threadLocalMain.set('主線程變量'); Thread t = new Thread() { @Override public void run() { super.run(); System.out.println( '現(xiàn)在獲取的變量是 =' + threadLocalMain.get()); } }; t.start();

運(yùn)行結(jié)果:

現(xiàn)在獲取的變量是 =主線程變量

上面這樣的代碼就能實(shí)現(xiàn)子父線程共享數(shù)據(jù)的情況,重點(diǎn)是使用InheritableThreadLocal來(lái)實(shí)現(xiàn)的共享。那么它是怎么實(shí)現(xiàn)數(shù)據(jù)共享的呢?在Thread類的init()方法中有這么一段代碼:

if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

這段代碼的意思是,在創(chuàng)建線程的時(shí)候,如果當(dāng)前線程的inheritThreadLocals變量和父線程的inheritThreadLocals變量都不為空的時(shí)候,會(huì)將父線程的inheritThreadLocals變量中的數(shù)據(jù),賦給當(dāng)前線程中的inheritThreadLocals變量。

ThreadLocal的內(nèi)存泄漏問(wèn)題上文我們也提到過(guò),ThreadLocal中的ThreadLocalMap里面的Entry對(duì)象是繼承自WeakReference類的,說(shuō)明Entry的key是一個(gè)弱引用。

Java中ThreadLocal的一些理解

弱引用是用來(lái)描述那些非必須的對(duì)象,弱引用的對(duì)象,只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。

這個(gè)弱引用還是ThreadLocal對(duì)象本身,所以一般在線程執(zhí)行完成后,ThreadLocal對(duì)象就會(huì)變成null了,而為null的弱引用對(duì)象,在下一次GC的時(shí)候就會(huì)被清除掉,這樣Entry的Key的內(nèi)存空間就被釋放出來(lái)了,但是Entry的value還在占用的內(nèi)存,如果線程是被復(fù)用的(例如線程池中的線程),那么這里面的value值就會(huì)越來(lái)越多,最終就導(dǎo)致了內(nèi)存泄漏。

防止內(nèi)存泄漏的辦法就是在每次使用完ThreadLocal的時(shí)候都去執(zhí)行以下remove()方法,就可以把key和value的空間都釋放了。

那既然容易產(chǎn)生內(nèi)存泄漏,為什么還要設(shè)置成弱引用的呢?如果正常情況下應(yīng)該是強(qiáng)引用,但是強(qiáng)引用只要引用關(guān)系還在就一直不會(huì)被回收,所以如果線程被復(fù)用了,那么Entry中的Key和Value都不會(huì)被回收,這樣就造成了Key和Value都會(huì)發(fā)生內(nèi)存泄漏了。

以上就是Java中ThreadLocal的一些理解的詳細(xì)內(nèi)容,更多關(guān)于Java ThreadLocal的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 桁架机器人_桁架机械手_上下料机械手_数控车床机械手-苏州清智科技装备制造有限公司 | 贴片电感_贴片功率电感_贴片绕线电感_深圳市百斯特电子有限公司 贴片电容代理-三星电容-村田电容-风华电容-国巨电容-深圳市昂洋科技有限公司 | 时代北利离心机,实验室离心机,医用离心机,低速离心机DT5-2,美国SKC采样泵-上海京工实业有限公司 工业电炉,台车式电炉_厂家-淄博申华工业电炉有限公司 | 车件|铜件|车削件|车床加工|五金冲压件-PIN针,精密车件定制专业厂商【东莞品晔】 | J.S.Bach 圣巴赫_高端背景音乐系统_官网| 安徽控制器-合肥船用空调控制器-合肥家电控制器-合肥迅驰电子厂 安徽净化板_合肥岩棉板厂家_玻镁板厂家_安徽科艺美洁净科技有限公司 | 上海小程序开发-小程序制作-上海小程序定制开发公司-微信商城小程序-上海咏熠 | 网站seo优化_seo云优化_搜索引擎seo_启新网络服务中心 | 捷码低代码平台 - 3D数字孪生_大数据可视化开发平台「免费体验」 | 手机存放柜,超市储物柜,电子储物柜,自动寄存柜,行李寄存柜,自动存包柜,条码存包柜-上海天琪实业有限公司 | 广州云仓代发-昊哥云仓专业电商仓储托管外包代发货服务 | 脉冲除尘器,除尘器厂家-淄博机械| 塑钢件_塑钢门窗配件_塑钢配件厂家-文安县启泰金属制品有限公司 深圳南财多媒体有限公司介绍 | ge超声波测厚仪-电动涂膜机-电动划格仪-上海洪富 | 木材烘干机,木炭烘干机,纸管/佛香烘干设备-河南蓝天机械制造有限公司 | 天津云仓-天津仓储物流-天津云仓一件代发-顺东云仓 | LED太阳能中国结|发光红灯笼|灯杆造型灯|节日灯|太阳能灯笼|LED路灯杆装饰造型灯-北京中海轩光电 | 洛阳网站建设_洛阳网站优化_网站建设平台_洛阳香河网络科技有限公司 | 电动手术床,医用护理床,led手术无影灯-曲阜明辉医疗设备有限公司 | LCD3D打印机|教育|桌面|光固化|FDM3D打印机|3D打印设备-广州造维科技有限公司 | 棉柔巾代加工_洗脸巾oem_一次性毛巾_浴巾生产厂家-杭州禾壹卫品科技有限公司 | 广州工业氧气-工业氩气-工业氮气-二氧化碳-广州市番禺区得力气体经营部 | 活性氧化铝|无烟煤滤料|活性氧化铝厂家|锰砂滤料厂家-河南新泰净水材料有限公司 | 深圳离婚律师咨询「在线免费」华荣深圳婚姻律师事务所专办离婚纠纷案件 | 韦伯电梯有限公司| 碳化硅,氮化硅,冰晶石,绢云母,氟化铝,白刚玉,棕刚玉,石墨,铝粉,铁粉,金属硅粉,金属铝粉,氧化铝粉,硅微粉,蓝晶石,红柱石,莫来石,粉煤灰,三聚磷酸钠,六偏磷酸钠,硫酸镁-皓泉新材料 | 比士亚-专业恒温恒湿酒窖,酒柜,雪茄柜的设计定制 | 液压升降货梯_导轨式升降货梯厂家_升降货梯厂家-河南东圣升降设备有限公司 | 作文导航网_作文之家_满分作文_优秀作文_作文大全_作文素材_最新作文分享发布平台 | 皮带机-带式输送机价格-固定式胶带机生产厂家-河南坤威机械 | 除甲醛公司-甲醛检测-广西雅居环境科技有限公司 | 网站优化公司_北京网站优化_抖音短视频代运营_抖音关键词seo优化排名-通则达网络 | 锂电池砂磨机|石墨烯砂磨机|碳纳米管砂磨机-常州市奥能达机械设备有限公司 | 博客-悦享汽车品质生活| 点胶机_点胶阀_自动点胶机_智能点胶机_喷胶机_点胶机厂家【欧力克斯】 | 机房监控|动环监控|动力环境监控系统方案产品定制厂家 - 迈世OMARA | 仿清水混凝土_清水混凝土装修_施工_修饰_保护剂_修补_清水混凝土修复-德州忠岭建筑装饰工程 | 立式硫化罐-劳保用品硫化罐-厂家直销-山东鑫泰鑫硫化罐厂家 | 压滤机-洗沙泥浆处理-压泥机-山东创新华一环境工程有限公司 | 防勒索软件_数据防泄密_Trellix(原McAfee)核心代理商_Trellix(原Fireeye)售后-广州文智信息科技有限公司 | 南京办公用品网-办公文具用品批发-打印机耗材采购 |