当前位置: 首页 > 科技观察

ThreadLocal的使用和实现原理

时间:2023-03-15 11:15:05 科技观察

前言ThreadLocal直译为本地线程,但实际上它的译名是ThreadLocalVariable。ThreadLocal的目的是隔离不同线程使用的变量。官方对它的解释是:它提供线程局部变量,独立于变量初始化副本,也就是说可以在某个线程中隔离某个变量。在内部,其他线程无法访问和使用这个变量。我们先做个测试,不使用ThreadLocal,创建三个线程:publicclassThreadLocalTest{publicstaticintnum=0;publicstaticintnumAdd(){返回num++;}publicstaticvoidmain(String[]args){Threadt1=newThread(newMyRunnable());Threadt2=newThread(newMyRunnable());Threadt3=newThread(newMyRunnable());t1。开始();t2.开始();t3.开始();}publicstaticclassMyRunnableimplementsRunnable{@Overridepublicvoidrun(){for(inti=0;i<3;i++){System.out.println(Thread.currentThread().getName()+"-"+ThreadLocalTest.numAdd());}}}}执行后发现控制台输出为:可以发现线程执行了numAdd()方法,从0-8运行了9次,num从0增加到8,也就是说静态变量在线程间共享,导致线程不安全。然后我们使用ThreadLocal来测试。publicclassThreadLocalTest{privatestaticThreadLocalthreadLocal=newThreadLocal(){@OverrideprotectedIntegerinitialValue(){返回0;}};publicstaticintnumAdd(){threadLocal.set(threadLocal.get()+1);返回threadLocal.get();}publicstaticvoidmain(String[]args){Threadt1=newThread(newMyRunnable());Threadt2=newThread(newMyRunnable());Threadt3=newThread(newMyRunnable());t1.开始();t2.开始();t3.开始();}publicstaticclassMyRunnableimplementsRunnable{@Overridepublicvoidrun(){for(inti=0;i<3;i++){System.out.println(Thread.currentThread().getName()+"-"+添加数());}}}}这里的numAdd方法使用了ThreadLocal的get()方法,这个方法调用了initialValue()方法,并将返回值设置为0。通过调用这个方法+1,达到了num++的效果。然后看输出结果。可以看出三个不同的线程是相互隔离的,变量的取值是互不相关的,也就是说,ThreadLocal使用不相关的变量,或者ThreadLocal为每个线程准备了一个变量副本,那么它是如何实现的呢,我们点进ThreadLocal的源码看看。这是ThreadLocal的组成,主要操作有get()和set()方法:get():返回当前ThreadLocal的值publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")T结果=(T)e.value;返回结果;}}返回setInitialValue();}set():将当前线程对象的值存入ThreadLocalMappublicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);否则createMap(t,value);}先创建当前Thread对象,然后存入ThreadLocalMap,判断map,如果不为空,则设置this(currentThreadlocalobject)为key,并获取对应的value,最后调用一个setInitialValue()方法得到初始化值。ThreadLocalMap介绍上面两个方法主要是介绍ThreadLocal的实现原理,即ThreadLocalMap的创建和使用。官方注释解释ThreadLocalMap是一个自定义的hashmap,只适用于维护ThreadLocal的值。ThreadLocal类之外没有导出操作。类是包私有的,以允许在类线程中声明字段。为了帮助处理非常大且长期存在的用途,哈希表条目使用对键的弱引用。但是,由于未使用引用队列,因此只有当表空间开始用完时,它才会开始删除陈旧的条目。点击ThreadLocalMap,可以看到ThreadLocalMap一开始定义了一个用于存储数据的Entry类。staticclassEntryextendsWeakReference>{/**与此ThreadLocal关联的值。*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}这个Entry类继承了弱引用类。众所周知,Java有四种引用类型,其中弱引用是指每次JVM进行垃圾回收时都会回收该对象,保证了ThreadLocal每次复制当前线程的值时占用的空间。可以重复使用。从get()方法可以知道,ThreadLocalMap的key(key)是ThreadLocal类的一个实例对象,value是user的值。那么ThreadLocalMap的引用在哪里呢?在上面的set()方法中,调用了getMap()和createMap()方法。ThreadLocalMapgetMap(Threadt){返回t.threadLocals;}voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);可以看到这里调用了一个名为threadLocals的属性,点击Thisattributewasfound跳转到Thread类。/*ThreadLocal值属于这个线程。该映射由ThreadLocal类维护*。*/ThreadLocal.ThreadLocalMapthreadLocals=null;所以这个属性就是ThreadLocalMap的引用,那么ThreadLocal的实现原理就很明确了:定义创建了一个ThreadLocalMap内部类,它使用Map的键值对来访问数据。key是ThreadLocal类的实例对象,value是传入的值。新建一个ThreadLocal对象,调用set()或get()方法,即调用ThreadLocalMap进行操作。使用ThreadLocal时,线程使用的变量是独占的(私有变量副本),其他线程无法访问。使用后(线程结束),这些变量会被GC回收。使用ThreadLocal的原因ThreadLocal可以用来共享实例变量作为全局变量,这样程序中的所有方法都可以访问变量。由于ThreadLocal中保存的变量是当前线程本身,其他线程无法访问,而ThreadLocal中保存的变量只是为了方便在程序中同线程之间传递这个变量(与解决线程安全无关)).