很多朋友对于TL是什么意思?用法、例句和从TL、ITL到TTL不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
ThreadLocal(TL)是Java中一种线程局部变量实现机制,他为每个线程提供一个单独的变量副本,保证多线程场景下,变量的线程安全。经常用于代替参数的显式传递。
InheritableThreadLocal(ITL)是JDK提供的TL增强版,而TransmittableThreadLocal(TTL)是阿里开源的ITL增强版
这些ThreadLocal在不同场景下有不同用途,我们来分析一下:
ThreadLocal主要的方法有四个:initialValue、set、get、remove
当线程首次访问该ThreadLocal时(ThreadLocal.get()),会进行初始化赋值。我们常用两种方法初始化ThreadLocal
ThreadLocal<String>threadLocal=newThreadLocal<String>(){\n@Override\nprotectedStringinitialValue(){\nreturn"";\n}\n};\n\n2.1.2、调用ThreadLocal.withInitial
ThreadLocal<String>threadLocal=ThreadLocal.withInitial(()->"");\n\n
他会创建一个SuppliedThreadLocal内部类
publicstatic<S>ThreadLocal<S>withInitial(Supplier<?extendsS>supplier){\nreturnnewSuppliedThreadLocal<>(supplier);\n}\n\n
该类重写了initialValue方法
staticfinalclassSuppliedThreadLocal<T>extendsThreadLocal<T>{\n\nprivatefinalSupplier<?extendsT>supplier;\n\nSuppliedThreadLocal(Supplier<?extendsT>supplier){\nthis.supplier=Objects.requireNonNull(supplier);\n}\n\n@Override\nprotectedTinitialValue(){\n//当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化\nreturnsupplier.get();\n}\n}\n\n
??ITL并没有重新实现withInitial,如果使用withInitial则会创建STL,失去自己增强的特性
publicvoidset(Tvalue){\nThreadt=Thread.currentThread();\nThreadLocalMapmap=getMap(t);\nif(map!=null)\nmap.set(this,value);\nelse\ncreateMap(t,value);\n}\n\n
这里出现了一个关键属性ThreadLocalMap,类定义在ThreadLocal中,是Thread的成员变量
ThreadLocalMapgetMap(Threadt){\nreturnt.threadLocals;\n}\n\n
ThreadLocalMap内部还有一个内部类Entry,是存值的地方
staticclassThreadLocalMap{\nstaticclassEntryextendsWeakReference<ThreadLocal<?>>{\nObjectvalue;\nEntry(ThreadLocal<?>k,Objectv){\n//ThreadLocal的引用是“key”\nsuper(k);\n//线程局部变量是value\nvalue=v;\n}\n}\n//Entry数组\n//value具体放在哪个index下,是由ThreadLocal的hashCode算出来的\nprivateEntry[]table;\n}\n\n2.3、取值——get
publicTget(){\nThreadt=Thread.currentThread();\n//1、获取线程的ThreadLocalMap\nThreadLocalMapmap=getMap(t);\nif(map!=null){\n//2、根据ThreadLocal的hashCode,获取对应Entry下的value\nThreadLocalMap.Entrye=map.getEntry(this);\nif(e!=null){\n@SuppressWarnings("unchecked")\nTresult=(T)e.value;\nreturnresult;\n}\n}\n//3、如果没有赋过值,则初始化\nreturnsetInitialValue();\n}\n\n2.4、清空——remove
publicvoidremove(){\nThreadLocalMapm=getMap(Thread.currentThread());\nif(m!=null)\n//会将对应Entry、包括他的key、value手动置null\nm.remove(this);\n}\n\n3、InheritableThreadLocal3.1、TL在父子线程场景下存在的问题
我们先来看一个例子
publicstaticvoidmain(String[]args)throwsInterruptedException{\nThreadLocal<String>threadLocal=ThreadLocal.withInitial(()->"A");\nthreadLocal.set("B");\nThreadthread=newThread(()->{\nSystem.out.println("子线程ThreadLocal:"+threadLocal.get());\n},"子线程");\nthread.start();\nthread.join();\n}\n\n
打印结果如下,可见子线程的ThreadLocal是初始值,并没有使用父线程修改后的值:
子线程ThreadLocal:A\n\n
线程的ThreadLocalMap是首次访问时创建的,所以子线程使用ThreadLocal的时候,会初始化一个新的ThreadLocal,线程局部变量为默认值
??所以,TL不具有遗传性
为了解决TL子线程遗传性的问题,JDK引入了ITL
他继承ThreadLocal,重写了childValue、getMap、createMap三个方法
publicclassInheritableThreadLocal<T>extendsThreadLocal<T>{\n\nprotectedTchildValue(TparentValue){\nreturnparentValue;\n}\n\nThreadLocalMapgetMap(Threadt){\nreturnt.inheritableThreadLocals;\n}\n\nvoidcreateMap(Threadt,TfirstValue){\nt.inheritableThreadLocals=newThreadLocalMap(this,firstValue);\n}\n}\n\n
这里出现了inheritableThreadLocals,他存储的就是从父线程拷贝过来的ThreadLocal,这个值是在父线程首次修改ThreadLocal的时候赋值的,然后在子线程创建时拷贝过来的
//父线程部分:\npublicvoidset(Tvalue){\nThreadt=Thread.currentThread();\n//该方法被ITL重写,访问inheritableThreadLocals为null\nThreadLocalMapmap=getMap(t);\nif(map!=null)\nmap.set(this,value);\nelse\n//该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals\ncreateMap(t,value);\n}\n\n//子线程部分:\npublicThread(Runnabletarget){\ninit(null,target,"Thread-"+nextThreadNum(),0);\n}\n\nprivatevoidinit(ThreadGroupg,Runnabletarget,Stringname,\nlongstackSize,AccessControlContextacc,\nbooleaninheritThreadLocals){\n//省略一些代码...\n\n//获取当前线程(父线程、也就是创建子线程的线程)\nThreadparent=currentThread();\n//1、允许ThreadLocal遗传(这个默认为true)\n//2、inheritableThreadLocals不为空,因为父线程调用set了\n//父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支\nif(inheritThreadLocals&&parent.inheritableThreadLocals!=null)\nthis.inheritableThreadLocals=\nThreadLocal.createInheritedMap(parent.inheritableThreadLocals);\n}\n\n//createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝\nprivateThreadLocalMap(ThreadLocalMapparentMap){\nEntry[]parentTable=parentMap.table;\nintlen=parentTable.length;\nsetThreshold(len);\ntable=newEntry[len];\n//深拷贝父线程ThreadLocalMap\nfor(intj=0;j<len;j++){\nEntrye=parentTable[j];\nif(e!=null){\n@SuppressWarnings("unchecked")\nThreadLocal<Object>key=(ThreadLocal<Object>)e.get();\nif(key!=null){\n//childValue被ITL重写,返回父线程ThreadLocal的值\nObjectvalue=key.childValue(e.value);\nEntryc=newEntry(key,value);\ninth=key.threadLocalHashCode&(len-1);\nwhile(table[h]!=null)\nh=nextIndex(h,len);\ntable[h]=c;\nsize++;\n}\n}\n}\n}\n\n
使用ITL的效果
publicstaticvoidmain(String[]args)throwsInterruptedException{\nThreadLocal<String>threadLocal=newInheritableThreadLocal<String>(){\n@Override\nprotectedStringinitialValue(){\nreturn"A";\n}\n};\nthreadLocal.set("B");\nThreadthread=newThread(()->{\nSystem.out.println("子线程ThreadLocal:"+threadLocal.get());\n},"子线程");\nthread.start();\n\nthread.join();\n}\n\n
打印结果如下,子线程拷贝了父线程ThreadLocal:
子线程ThreadLocal:B
总结一下,ITL解决父子线程遗传性的核心思路是,将可遗传的ThreadLocal放在父线程新的ThreadLocalMap中,在子线程首次使用时进行拷贝
publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{\nThreadLocal<String>threadLocal=newInheritableThreadLocal<String>(){\n@Override\nprotectedStringinitialValue(){\nreturn"A";\n}\n};\nthreadLocal.set("B");\nExecutorServiceexecutorService=Executors.newFixedThreadPool(1);\n//1、子线程第一次获取ThreadLocal\nexecutorService.submit(()->System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();\nThread.sleep(1000);\n//2、父线程修改ThreadLocal\nthreadLocal.set("C");\nSystem.out.println("父线程修改ThreadLocal为"+threadLocal.get());\n//3、子线程第二次获取ThreadLocal\nexecutorService.submit(()->System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();\n}\n\n
打印结果如下,子线程在第二次打印时,并没有拷贝父线程的ThreadLocal,使用的还是首次拷贝的值:
子线程ThreadLocal:B\n父线程修改ThreadLocal为C\n子线程ThreadLocal:B
??可复用的子线程不会感知父线程ThreadLocal的变化
TTL在ITL上做了稍微复杂的封装,我们从使用开始了解
<dependency>\n<groupId>com.alibaba</groupId>\n<artifactId>transmittable-thread-local</artifactId>\n<version>latest</version>\n</dependency>\n\n
在使用TTL时,线程需要经过TTL封装,线程池同理
publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{\nThreadLocal<String>threadLocal=newTransmittableThreadLocal<String>(){\n@Override\nprotectedStringinitialValue(){\nreturn"A";\n}\n};\nthreadLocal.set("B");\nExecutorServiceexecutorService=TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));\nexecutorService.submit(()->System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();\nThread.sleep(1000);\nthreadLocal.set("C");\nSystem.out.println("父线程修改ThreadLocal为"+threadLocal.get());\nexecutorService.submit(()->System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();\nThread.sleep(1000);\nexecutorService.submit(()->{\nthreadLocal.set("D");\nSystem.out.println("子线程修改ThreadLocal为"+threadLocal.get());\n});\nThread.sleep(1000);\nexecutorService.submit(()->System.out.println("子线程ThreadLocal:"+threadLocal.get()));\nThread.sleep(1000);\n}\n\n
打印结果如下,子线程每次都会获取父线程的ThreadLocal
子线程ThreadLocal:B\n父线程修改ThreadLocal为C\n子线程ThreadLocal:C\n子线程修改ThreadLocal为D\n子线程ThreadLocal:C
从使用上看,TTL要求将任务封装,那我们就从ThreadLocal和ExecutorService两部分入手
下面是TTL的取值和赋值逻辑,都涉及一个关键方法addThisToHolder,对应的属性holder会在线程池执行任务时用到
//TransmittableThreadLocal.addThisToHolder()\nprivatevoidaddThisToHolder(){\n//InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>>holder\nif(!holder.get().containsKey(this)){\n//holder是静态变量,他会把TTL存到当前线程的map中\n//value是null,他其实是把Map当Set用\n//主线程赋值时,会获取主线程的holderMap,然后把TTL存进去\nholder.get().put((TransmittableThreadLocal<Object>)this,null);\n}\n}\n\n@Override\npublicfinalvoidset(Tvalue){\nif(!disableIgnoreNullValueSemantics&&null==value){\nremove();\n}else{\nsuper.set(value);\n//当主线程赋值时,会将自己的TTL放到自己的map中\naddThisToHolder();\n}\n}\n\n@Override\npublicfinalTget(){\nTvalue=super.get();\nif(disableIgnoreNullValueSemantics||null!=value)\naddThisToHolder();\nreturnvalue;\n}\n\n4.2.3、TTL对任务的封装
//我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装\npublicstaticExecutorServicegetTtlExecutorService(@NullableExecutorServiceexecutorService){\nif(TtlAgent.isTtlAgentLoaded()||executorService==null||executorServiceinstanceofTtlEnhanced){\nreturnexecutorService;\n}\n//入参是线程池,通过包装类代理线程池的操作\nreturnnewExecutorServiceTtlWrapper(executorService);\n}\n\n//ExecutorServiceTtlWrapper.submit()\npublicFuture<?>submit(@NonNullRunnabletask){\n//将提交的任务进行封装\nreturnexecutorService.submit(TtlRunnable.get(task));\n}\n\n4.2.3.1、任务构建
TtlRunnable构造方法
这里都是主线程在操作,因为任务是主线程提交的
privateTtlRunnable(@NonNullRunnablerunnable,booleanreleaseTtlValueReferenceAfterRun){\nthis.capturedRef=newAtomicReference<Object>(capture());\nthis.runnable=runnable;\nthis.releaseTtlValueReferenceAfterRun=releaseTtlValueReferenceAfterRun;\n}\n\n
这里有一个关键属性capturedRef,他是一个原子引用,存了TTL
//TrasmitableThreadLocal.Transmitter\npublicstaticObjectcapture(){\n//获取ttl的值构建快照\nreturnnewSnapshot(captureTtlValues(),captureThreadLocalValues());\n}\n\nprivatestaticHashMap<TransmittableThreadLocal<Object>,Object>captureTtlValues(){\nHashMap<TransmittableThreadLocal<Object>,Object>ttl2Value=newHashMap<TransmittableThreadLocal<Object>,Object>();\nfor(TransmittableThreadLocal<Object>threadLocal:holder.get().keySet()){\n//将主线程TTL的值存到当前任务中\nttl2Value.put(threadLocal,threadLocal.copyValue());\n}\nreturnttl2Value;\n}\n\n4.2.3.2、任务执行
任务执行的代码如下,在任务执行前回放ThreadLocal,在任务执行后恢复ThreadLocal:
这里都是子线程在操作,因为任务都是子线程执行的
@Override\npublicvoidrun(){\nObjectcaptured=capturedRef.get();\nif(captured==null||releaseTtlValueReferenceAfterRun&&!capturedRef.compareAndSet(captured,null)){\nthrownewIllegalStateException("TTLvaluereferenceisreleasedafterrun!");\n}\n//1、备份子线程ThreadLocal\n//2、使用主线程提交任务时构建的ThreadLocal副本,将子线程ThreadLocal覆盖\nObjectbackup=replay(captured);\ntry{\n//3、任务执行\nrunnable.run();\n}finally{\n//3、使用之前备份的子线程ThreadLocal进行恢复\nrestore(backup);\n}\n}\n\n
总结一下,TTL让子线程感知父线程变化的核心思路是,主线程在任务提交时构建ThreadLocal副本,在子线程执行任务时供其使用
??提交和执行任务会对TTL进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略
来源:京东云开发者自猿其说Tech
关于TL是什么意思?用法、例句到此分享完毕,希望能帮助到您。