您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 信息化管理 > 深入JDK源码之ThreadLocal类
深入JDK源码之ThreadLocal类学习JDK中的类,首先看下JDKAPI对此类的描述,描述如下:该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal其实就是一个工具类,用来操作线程局部变量,ThreadLocal实例通常是类中的privatestatic字段。它们希望将状态与某一个线程(例如,用户ID或事务ID)相关联。例如,以下类生成对每个线程唯一的局部标识符。线程ID是在第一次调用UniqueThreadIdGenerator.getCurrentThreadId()时分配的,在后续调用中不会更改。其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。importjava.util.concurrent.atomic.AtomicInteger;publicclassUniqueThreadIdGenerator{privatestaticfinalAtomicIntegeruniqueId=newAtomicInteger(0);privatestaticfinalThreadLocalIntegeruniqueNum=newThreadLocalInteger(){@OverrideprotectedIntegerinitialValue(){returnuniqueId.getAndIncrement();}};publicstaticintgetCurrentThreadId(){returnuniqueId.get();}}从线程的角度看,每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且ThreadLocal实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。API表达了下面几种观点:ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。ThreadLocal在类中通常定义为静态类变量。每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。既然定义为类变量,为何为每个线程维护一个副本(姑且成为‘拷贝’容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是空间换时间,synchronized顺序执行是时间换取空间。ThreadLocal介绍从字面上来理解ThreadLocal,感觉就是相当于线程本地的。我们都知道,每个线程在jvm的虚拟机里都分配有自己独立的空间,线程之间对于本地的空间是相互隔离的。那么ThreadLocal就应该是该线程空间里本地可以访问的数据了。ThreadLocal变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。很多人看到这里会容易产生一种错误的印象,感觉是不是这个ThreadLocal对象建立了一个类似于全局的map,然后每个线程作为map的key来存取对应线程本地的value。你看,每个线程不一样,所以他们映射到map中的key应该也不一样。实际上,如果我们后面详细分析ThreadLocal的代码时,会发现不是这样的。它具体是怎么实现的呢?在此输入图片描述ThreadLocal源码ThreadLocal类本身定义了有get(),set()和initialValue()三个方法。前面两个方法是public的,initialValue()是protected的,主要用于我们在定义ThreadLocal对象的时候根据需要来重写。这样我们初始化这么一个对象在里面设置它的初始值时就用到这个方法。ThreadLocal变量因为本身定位为要被多个线程来访问,它通常被定义为static变量。ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个Map为每个线程复制一个变量的‘拷贝’存储其中。当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量,代码如下:publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null)return(T)e.value;}returnsetInitialValue();}调用get方法如果此Map不存在首先初始化,创建此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,我们可以覆盖此方法,在首次调用时初始化一个适当的值。setInitialValue代码如下:privateTsetInitialValue(){Tvalue=initialValue();Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);returnvalue;}set方法相对比较简单如果理解以上俩个方法,获取当前线程的引用,从map中获取该线程对应的map,如果map存在更新缓存值,否则创建并存储,代码如下:publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);}对于ThreadLocal在何处存储变量副本,我们看getMap方法:获取的是当前线程的ThreadLocal类型的threadLocals属性。显然变量副本存储在每一个线程中。/***获取线程的ThreadLocalMap属性实例*/ThreadLocalMapgetMap(Threadt){returnt.threadLocals;}上面我们知道变量副本存放于何处,这里我们简单说下如何被java的垃圾收集机制收集,当我们不在使用时调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操作,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。注意:如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。看到ThreadLocal类中的变量只有这3个int型:/***ThreadLocalsrelyonper-threadlinear-probehashmapsattached*toeachthread(Thread.threadLocalsand*inheritableThreadLocals).TheThreadLocalobjectsactaskeys,*searchedviathreadLocalHashCode.Thisisacustomhashcode*(usefulonlywithinThreadLocalMaps)thateliminatescollisions*inthecommoncasewhereconsecutivelyconstructedThreadLocals*areusedbythesamethreads,whileremainingwell-behavedin*lesscommoncases.*/privatefinalintthreadLocalHashCode=nextHashCode();/***Thenexthashcodetobegivenout.Updatedatomically.Startsat*zero.*/privatestaticAtomicIntegernextHashCode=newAtomicInteger();/***Thedifferencebetweensuccessivelygeneratedhashcodes-turns*implicitsequentialthread-localIDsintonear-optimallyspread*multiplicativehashvaluesforpower-of-two-sizedtables.*/privatestaticfinalintHASH_INCREMENT=0x61c88647;而作为ThreadLocal实例的变量只有threadLocalHashCode这一个,nextHashCode和HASH_INCREMENT是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode的值。现在来看看它的哈希策略。所有ThreadLocal对象共享一个AtomicInteger对象nextHashCode用于计算hashcode,一个新对象产生时它的hashcode就确定了,算法是从0开始,以HASH_INCREMENT=0x61c88647为间隔递增,这是ThreadLocal唯一需要同步的地方。根据hashcode定位桶的算法是将其与数组长度-1进行与操作:key.threadLocalHashCode&(table.length-1)。0x61c88647这个魔数是怎么确定的呢?ThreadLocalMap的初始长度为16,每次扩容都增长为原来的2倍,即它的长度始终是2的n次方,上述算法中使用0x61c88647可以让hash的结果在2的n次方内尽可能均匀分布,减少冲突的概率。可以来看一下创建一个ThreadLocal实例即newThreadLocal()时做了哪些操作,从上面看到构造函数ThreadLocal()里什么操作都没有,唯一的操作是这句:privatefinalintthreadLocalHashCode=nextHashCode();那么nextHashCode()做了什么呢:privatestaticsynchronizedintnextHashCode(){inth=nextHashCode;nextHashCode=h+HASH_INCREMENT;returnh;}就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。因此Thread
本文标题:深入JDK源码之ThreadLocal类
链接地址:https://www.777doc.com/doc-2240387 .html