当前位置: 首页 > 后端技术 > Java

ThreadLocal的使用及原理分析

时间:2023-04-01 21:04:02 Java

基本上是使用JDK的lang包下提供的ThreadLocal类,我们可以用它来创建线程变量,线程变量的作用域只在本线程内。用2个例子来展示ThreadLocal的用法。示例1:ThreadLocalthreadLocal=newThreadLocal<>();System.out.println(threadLocal.get());threadLocal.set(1);System.out.println(threadLocal.get());threadLocal.remove();System.out.println(threadLocal.get());输出:null1null此示例显示了ThreadLocal提供的所有方法。ThreadLocal中提供了三个方法,分别是:get:获取变量值set:设置变量值remove:删除变量值示例2://创建一个MyRun类classMyRunimplementsRunnable{//创建2个线程变量,var1,var2privateThreadLocal<整数>var1=newThreadLocal<>();privateThreadLocalvar2=newThreadLocal<>();@Overridepublicvoidrun(){//调用m方法5次for(inti=0;i<5;i++){m();}}publicvoidm(){//当前线程名Stringname=Thread.currentThread().getName();//var1变量从1开始,每次调用m加1Integerv=var1.get();如果(v==null){var1.set(1);}else{var1.set(v+1);}//var2变量=线程名-var1值var2.set(name+"-"+var1.get());//打印打印();}publicvoidprint(){字符串名称=Thread.currentThread().getName();系统输出。println(名称+",var1:"+var1.get()+",var2:"+var2.get());}}创建2个线程并执行相同的MyRun:MyRunmyRun=newMyRun();Threadt1=newThread(myRun);Threadt2=newThread(myRun);t1.start();t2.start();输出:Thread-0,var1:1,var2:Thread-0-1Thread-1,var1:1,var2:Thread-1-1Thread-0,var1:2,var2:Thread-0-2Thread-1,var1:2、var2:Thread-1-2Thread-0,var1:3,var2:Thread-0-3Thread-1,var1:3,var2:Thread-1-3Thread-0,var1:4,var2:Thread-0-4Thread-0,var1:5,var2:Thread-0-5Thread-1,var1:4,var2:Thread-1-4Thread-1,var1:5,var2:Thread-1-5示例2显示了一个重要特征ThreadLocal:两个线程执行同一个MyRun对象,如果var1和var2是公共成员变量,两个线程会访问同一个变量,会造成线程安全问题。但是从输出日志来看,t1和t2的var1和var2值其实是独立的,互不影响。这是因为var1和var2是ThreadLocal类型,即线程变量,绑定到线程。无论哪个线程访问这段代码,都会从哪个线程获取var1和var2的变量值。相互隔离,不存在线程安全问题。原理分析ThreadLocal是如何实现这个效果的?我们可以从ThreadLocal的源码中了解到。其中,最重要的是get方法。我把get相关的源码摘录如下:publicTget(){//获取当前线程对象Threadt=Thread.currentThread();//从当前线程获取ThreadLocalMap对象ThreadLocalMapmap=getMap(t);if(map!=null){//从ThreadLocalMap对象中获取当前ThreadLocal对应的EntryThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){//如果Entry不为null,则返回值@SuppressWarnings("unchecked")Tresult=(T)e.value;返回结果;}}//如果获取的ThreadLocalMap对象为null,则返回默认值returnsetInitialValue();}//从指定线程对象中获取ThreadLocalMap,即ThreadThreadLocalMap中的threadLocalsgetMap(Threadt){returnt.threadLocals;}//默认值privateTsetInitialValue(){Tvalue=initialValue();线程t=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);//如果当前线程的threadLocals不为null,则赋默认值elsecreateMap(t,value);//如果当前线程的如果threadLocals为null,创建一个新的返回值;}voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}protectedTinitialValue(){returnnull;//初始值为null}从上面代码可以看出,ThreadLocal访问的其实是当前线程的成员变量threadLocals。threadLocals的数据类型是ThreadLocalMap,是JDK中专门为ThreadLocal设计的数据结构。它本质上是一种键值对类型。ThreadLocalMap的key存放的是当前的ThreadLocal对象,value是ThreadLocal对象实际存放的值。在使用ThreadLocal对象的Get方法时,实际上是从当前线程的threadLocals中获取key到当前ThreadLocal对象对应的value。画个图帮你理解:理解了ThreadLocal的get原理,set和remove方法不用看源码也能猜到怎么写。无非就是将ThreadLocal对象的值设置为键或者删除键值对。ThreadLocal的初始值上面介绍过,我们看到ThreadLocal的initialValue方法总是返回null:protectedTinitialValue(){returnnull;//初始值为null}如果想设置ThreadLocal对象的初始值,可以使用下面的方法:ThreadLocalthreadLocal=ThreadLocal.withInitial(()->1);System.out.println(threadLocal.get());withInitial方法中实际返回的是一个ThreadLocal子类SuppliedThreadLocal对象。SuppliedThreadLocal覆盖ThreadLocal的initialValue方法。staticfinalclassSupplierThreadLocalextendsThreadLocal{privatefinalSupplier供应商;SupplierThreadLocal(Suppliersupplier){this.supplier=Objects.requireNonNull(supplier);}@OverrideprotectedTinitialValue(){returnsupplier.get();}}获取父线程的ThreadLocal变量在某些场景下,我们可能需要子线程能够获取父线程的ThreadLocal变量,但是使用ThreadLocal是获取不到的:publicstaticThreadLocalthreadLocal=new线程局部<>();publicstaticvoidmain(String[]args){threadLocal.set(1);System.out.println(threadLocal.get());ThreadchildThread=newThread(()->System.out.println(threadLocal.get()));childThread.start();}Output:1null使用ThreadLocal子类InheritableThreadLocal可以实现这个效果:publicstaticThreadLocalthreadLocal=newInheritableThreadLocal<>();publicstaticvoidmain(String[]args){threadLocal.set(1);System.out.println(threadLocal.get());ThreadchildThread=newThread(()->System.out.println(threadLocal.get()));childThread.start();}11InheritableThreadLocal是怎么做到的?下面来分析一下InheritableThreadLocal的源码}ThreadLocalMapgetMap(Threadt){returnt.inheritableThreadLocals;}voidcreateMap(Threadt,TfirstValue){t.inheritableThreadLocals=newThreadLocalMap(this,firstValue);InheritableThreadLocal的源码不多,主要涵盖三个ThreadLocal方法childValue、getMap和createMap。childValue方法由ThreadLocalMap在内部使用。ThreadLocalMap的内部设计我们无意讲解,这里可以忽略;ThreadLocal本来就是getMap和createMap读写当前Thread对象的threadLocals变量。而InheritableThreadLocal将其改为读写当前Thread对象的InheritableThreadLocal变量。那我们就得从Thread类的源码中寻找蛛丝马迹了。在Thread类的源码中,我们可以看到有两个成员变量:ThreadLocal.ThreadLocalMapthreadLocals=null;ThreadLocal.ThreadLocalMapinheritableThreadLocals=null;如果使用ThreadLocal创建线程变量,则读写Thread对象的threadLocals;if使用InheritableThreadLocal创建线程变量,读写Thread对象的inheritableThreadLocals。在Thread类的init方法中可以看到(Thread的所有构造方法都调用了init方法,这里只贴出关键部分):if(parent.inheritableThreadLocals!=null)this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);ThreadLocal.createInheritedMap:staticThreadLocalMapcreateInheritedMap(ThreadLocalMapparentMap){returnnewThreadLocalMap(parentMap);}如果父线程的inheritableThreadLocals不为null,则将父线程的inheritableThreadLocals赋值给的inheritableThreadLocals变量当前线程。总结:使用InheritableThreadLocal创建线程变量时,父线程读写线程变量实际上是写入了父线程的inheritableThreadLocals。在创建子线程的时候,会将父线程的inheritableThreadLocals复制到子线程的inheritableThreadLocals中,子线程操作这个线程的变量读写时,也是自己线程的inheritableThreadLocals,实现了效果是子线程可以获得父线程的ThreadLocal。其他要点如果使用线程池,线程会被复用,所以线程的threadLocals和inheritableThreadLocals也会被复用。在线程池中使用ThreadLocal可能会出现一些问题,需要注意;JDK本身提供了创建线程池的方法,不支持获取父线程的ThreadLocal变量。

最新推荐
猜你喜欢