澳门新葡萄京娱乐场 47

ThreadLocal源码分析(JDK8)

引言

ThreadLocal的官方API解释为:

“该类提供了线程局部 (thread-local)
变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的
private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务
ID)相关联。”

大概的意思有两点:

  1. ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离。
  2. 如果要使用ThreadLocal,通常定义为private
    static类型,在我看来最好是定义为private static final类型。

前言:

         
相信读者在网上也看了很多关于ThreadLocal的资料,很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路;ThreadLocal的目的是为了解决多线程访问资源时的共享问题。如果你也这样认为,那现在给你10秒钟,清空之前对ThreadLocal的错误认知!

应用场景

ThreadLocal通常用来共享数据,当你想在多个方法中使用某个变量,这个变量是当前线程的状态,其它线程不依赖这个变量,你第一时间想到的就是把变量定义在方法内部,然后再方法之间传递参数来使用,这个方法能解决问题,但是有个烦人的地方就是,每个方法都需要声明形参,多处声明,多处调用。影响代码的美观和维护。有没有一种方法能将变量像private
static形式来访问呢?这样在类的任何一处地方就都能使用。这个时候ThreadLocal大显身手了。

ThreadLocal源码分析(JDK8)

澳门新葡萄京娱乐场 1分类:

Java(20)
澳门新葡萄京娱乐场 2澳门新葡萄京娱乐场 3

作者同类文章X

JDK源码分析(7)
澳门新葡萄京娱乐场 4澳门新葡萄京娱乐场 5

作者同类文章X

版权声明:【分享也是一种提高】个人转载请在正文开头明显位置注明出处,未经作者同意禁止企业/组织转载,禁止私自更改原文,禁止用于商业目的。

ThreadLocal特性及使用场景:

1、方便同一个线程使用某一对象,避免不必要的参数传递;

2、线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);

3、获取数据库连接、Session、关联ID(比如日志的uniqueID,方便串起多个日志);

ThreadLocal应注意:

1、ThreadLocal并未解决多线程访问共享对象的问题;

2、ThreadLocal并不是每个线程拷贝一个对象,而是直接new(新建)一个;

3、如果ThreadLocal.set()的对象是多线程共享的,那么还是涉及并发问题。

1、ThreadLocal<T>初始化

private final int
threadLocalHashCode = nextHashCode();

private static final intHASH_INCREMENT=0x61c88647;

/**

* The next hash code to be given out. Updated atomically.Starts at zero.

*/

//
源码说nextHashCode初始值为0,但实际调试时显示初始值为1253254570,费解?

// 而且当初始化完毕后,nextHashCode的值又变为0,说明其初始值确实是0的。

private static AtomicInteger nextHashCode = new AtomicInteger();  
private static intnextHashCode() {  
    return nextHashCode.getAndAdd(HASH_INCREMENT);  
} 




private static AtomicInteger nextHashCode = new AtomicInteger();
private static intnextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

ThreadLocal类变量有3个,其中2个是静态变量(包括一个常量),实际作为作为ThreadLocal实例的变量只有threadLocalHashCode这1个,而且已经初始化就不可变了。

创建ThreadLocal实例时有哪些操作呢:

ThreadLocal初始化时会调用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode初始化后不可变。threadLocalHashCode可用来标记不同的ThreadLocal实例。


2、内部类

2.1 ThreadLocalMap

ThreadLocalMap是定制的hashMap,仅用于维护当前线程的本地变量值。仅ThreadLocal类对其有操作权限,是Thread的私有属性。为避免占用空间较大或生命周期较长的数据常驻于内存引发一系列问题,hash
table的key是弱引用WeakReferences。当空间不足时,会清理未被引用的entry。

ThreadLocalMap中的重点:

static class Entryextends WeakReference<ThreadLocal<?>>{  
   /** The value associated with this ThreadLocal. */  
    Object value;  
     Entry(ThreadLocal<?> k, Object v){  
       super(k);  
        value= v;  
   }  
} 




static class Entryextends WeakReference<ThreadLocal<?>>{
   /** The value associated with this ThreadLocal. */
    Object value;
     Entry(ThreadLocal<?> k, Object v){
       super(k);
        value= v;
   }
}

Note:

ThreadLocalMap的key是ThreadLocal,value是Object(即我们所谓的“线程本地数据”)。

2.2 SuppliedThreadLocal<T> extends
ThreadLocal<T>

SuppliedThreadLocal是JDK8新增的内部类,只是扩展了ThreadLocal的初始化值的方法而已,允许使用JDK8新增的Lambda表达式赋值。需要注意的是,函数式接口Supplier不允许为null。

源码如下:

static final class SuppliedThreadLocal<T>extends ThreadLocal<T>{  
    private final Supplier<?extends T> supplier;  
     SuppliedThreadLocal(Supplier<?extends T> supplier){  
       this.supplier= Objects.requireNonNull(supplier);  
   }  
     @Override  
   protected T initialValue(){  
       return supplier.get();  
   }  
} 




static final class SuppliedThreadLocal<T>extends ThreadLocal<T>{
    private final Supplier<?extends T> supplier;
     SuppliedThreadLocal(Supplier<?extends T> supplier){
       this.supplier= Objects.requireNonNull(supplier);
   }
     @Override
   protected T initialValue(){
       return supplier.get();
   }
}

3、主要方法

3.1、T get()

返回当前线程的value。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 6澳门新葡萄京娱乐场 7

  1. public T get(){  
  2.     Thread t= Thread.currentThread(); // 获取当前线程  
  3.     ThreadLocalMap map= getMap(t); // 获取当前线程对应的Map  
  4.    if(map!=null){  
  5.         ThreadLocalMap.Entry e = map.getEntry(this); // 详见3.1.1  
  6.        if(e!=null){ // map不为空且当前线程有value,返回value  
  7.             @SuppressWarnings(“unchecked”)  
  8.             T result=(T)e.value;  
  9.            return result;  
  10.        }  
  11.    }  
  12.    return setInitialValue(); // 初始化再返回值  
  13. }  

澳门新葡萄京娱乐场 8

public T get(){
    Thread t= Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map= getMap(t); // 获取当前线程对应的Map
   if(map!=null){
        ThreadLocalMap.Entry e = map.getEntry(this); // 详见3.1.1
       if(e!=null){ // map不为空且当前线程有value,返回value
            @SuppressWarnings("unchecked")
            T result=(T)e.value;
           return result;
       }
   }
   return setInitialValue(); // 初始化再返回值
}

getMap的源码:

[java] view
plain
copy

print?澳门新葡萄京娱乐场 9澳门新葡萄京娱乐场 10

  1. ThreadLocalMap getMap(Thread t){  
  2.    returnt.threadLocals;  
  3. }  

澳门新葡萄京娱乐场 11

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

getMap(t)返回当前线程的成员变量ThreadLocalMap(Thread的成员变量有ThreadLocalMap,这一点可以查看Thread的源码,如下)很明确的说明了ThreadLocal属于线程,ThreadLocalMap由ThreadLocal持有,说到底,ThreadLocalMap
也是线程所持有。每个线程Thread都有自己的ThreadLocalMap。

/* ThreadLocal values pertaining to this thread. This map is maintained
by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;


setInitialValue源码:

[java] view
plain
copy

print?澳门新葡萄京娱乐场 12澳门新葡萄京娱乐场 13

  1. private T setInitialValue(){  
  2.     T value= initialValue(); //调用重写的initialValue,返回新值  
  3.     Thread t= Thread.currentThread();  
  4.     ThreadLocalMap map= getMap(t);  
  5.    if(map!=null) // 当前线程的ThreadLocalMap不为空,则直接赋值  
  6.         map.set(this, value);  
  7.    else  
  8. // 为当前线程创造一个ThreadLocalMap(this, firstValue)并赋初值,this为当前线程  
  9.         createMap(t, value);  
  10.    return value;  
  11. }  
  12. protected T initialValue() {  
  13.     return T; // 自定义返回值  
  14. };  

澳门新葡萄京娱乐场 14

private T setInitialValue(){
    T value= initialValue(); //调用重写的initialValue,返回新值
    Thread t= Thread.currentThread();
    ThreadLocalMap map= getMap(t);
   if(map!=null) // 当前线程的ThreadLocalMap不为空,则直接赋值
        map.set(this, value);
   else
// 为当前线程创造一个ThreadLocalMap(this, firstValue)并赋初值,this为当前线程
        createMap(t, value);
   return value;
}
protected T initialValue() {
    return T; // 自定义返回值
};

createMap源码:

[java] view
plain
copy

print?澳门新葡萄京娱乐场 15澳门新葡萄京娱乐场 16

  1. void createMap(Thread t, T firstValue){  
  2.     t.threadLocals=new ThreadLocalMap(this, firstValue);  
  3. }  

澳门新葡萄京娱乐场 17

void createMap(Thread t, T firstValue){
    t.threadLocals=new ThreadLocalMap(this, firstValue);
}

ThreadLocal之get流程:

1、获取当前线程t;

2、返回当前线程t的成员变量ThreadLocalMap(以下简写map);

3、map不为null,则获取以当前线程为key的ThreadLocalMap的Entry(以下简写e),如果e不为null,则直接返回该Entry的value;

4、如果map为null或者e为null,返回setInitialValue()的值。setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回默认值null),并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)。


3.2、void set(T value)

为【当前线程】的【当前ThreadLocal】赋值(初始值or新值)。和setInitialValue相当相似,就不多分析了。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 18澳门新葡萄京娱乐场 19

  1. public void set(T value){  
  2.     Thread t= Thread.currentThread();  
  3.     ThreadLocalMap map= getMap(t);  
  4.    if(map!=null)  
  5.         map.set(this, value);  
  6.    else  
  7.         createMap(t, value);  
  8. }  

澳门新葡萄京娱乐场 20

public void set(T value){
    Thread t= Thread.currentThread();
    ThreadLocalMap map= getMap(t);
   if(map!=null)
        map.set(this, value);
   else
        createMap(t, value);
}

3.3、void
remove()

获取当前线程的ThreadLocalMap,map不为空,则移除当前ThreadLocal作为key的键值对。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 21澳门新葡萄京娱乐场 22

  1. public void remove(){  
  2.      ThreadLocalMap m= getMap(Thread.currentThread());  
  3.     if(m!=null)  
  4.          m.remove(this);  
  5.  }  

澳门新葡萄京娱乐场 23

public void remove(){
     ThreadLocalMap m= getMap(Thread.currentThread());
    if(m!=null)
         m.remove(this);
 }

Note:

remove()移除当前线程的当前ThreadLocal数据(只是清空该key-value键值对),而且是立即移除,移除后,再调用get方法将重新调用initialValue方法初始化(除非在此期间调用了set方法赋值)。


3.4、static <S>
ThreadLocal<S> withInitial(Supplier<? extends S>
supplier)

JDK8新增,支持Lambda表达式,和ThreadLocal重写的initialValue()效果一样。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 24澳门新葡萄京娱乐场 25

  1. public static<S> ThreadLocal<S> withInitial(Supplier<?extends S> supplier){  
  2.    return new SuppliedThreadLocal<>(supplier);  
  3. }  

澳门新葡萄京娱乐场 26

public static<S> ThreadLocal<S> withInitial(Supplier<?extends S> supplier){
   return new SuppliedThreadLocal<>(supplier);
}

可以看出,withInitial()方法的入参是函数式接口Supplier,返回值是JDK8新增的内部类SuppliedThreadLocal,正如2.2所说,区别仅在于支持Lambda表达式赋值而已。使用事例如下:

[java] view
plain澳门新葡萄京娱乐场
copy

print?澳门新葡萄京娱乐场 27澳门新葡萄京娱乐场 28

  1. @Test  
  2. public void jdk8Test(){  
  3.     Supplier<String> supplier =new Supplier<String>(){  
  4.          @Override  
  5.        public String get(){  
  6.            return”supplier_new”;  
  7.        }  
  8.    };  
  9.     threadLocal= ThreadLocal.withInitial(supplier);  
  10.     System.out.println(threadLocal.get());// supplier_new  
  11.     threadLocal= ThreadLocal.withInitial(()->”sup_new_2″);  
  12.     System.out.println(threadLocal.get());// sup_new_2  
  13.     ThreadLocal<DateFormat> localDate = ThreadLocal.withInitial(()->new SimpleDateFormat(“yyyy-MM-dd”));  
  14.     System.out.println(localDate.get().format(new Date()));// 2017-01-22  
  15.     ThreadLocal<String> local =new ThreadLocal<>().withInitial(supplier);  
  16.     System.out.println(local.get());// supplier_new  
  17. }  

澳门新葡萄京娱乐场 29

@Test
public void jdk8Test(){
    Supplier<String> supplier =new Supplier<String>(){
         @Override
       public String get(){
           return"supplier_new";
       }
   };
    threadLocal= ThreadLocal.withInitial(supplier);
    System.out.println(threadLocal.get());// supplier_new
    threadLocal= ThreadLocal.withInitial(()->"sup_new_2");
    System.out.println(threadLocal.get());// sup_new_2
    ThreadLocal<DateFormat> localDate = ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));
    System.out.println(localDate.get().format(new Date()));// 2017-01-22
    ThreadLocal<String> local =new ThreadLocal<>().withInitial(supplier);
    System.out.println(local.get());// supplier_new
}

Note:

·withInitial(supplier)是有返回值ThreadLocal的,So实例化时需将其赋值给ThreadLocal实例。


4、图解**ThreadLocal**

每个线程可能有多个ThreadLocal,同一线程的各个ThreadLocal存放于同一个ThreadLocalMap中。

澳门新葡萄京娱乐场 30

澳门新葡萄京娱乐场 31

图解ThreadLocal(JDK8).vsdx原图下载地址:https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/threadLocal


5、**ThreadLocal-ThreadLocalMap源码分析**

5.1、Entry getEntry(ThreadLocal<?>
key)

首先来看get方法,你会发现ThreadLocalMap的get方法和传统Map不同,其返回的不是key-value的value,而是整个entry,当时entry的key是ThreadLocal,value是存放的值,这点是一致的。

a、getEntry源码分析:

[java] view
plain
copy

print?澳门新葡萄京娱乐场 32澳门新葡萄京娱乐场 33

  1. private Entry getEntry(ThreadLocal<?> key){  
  2.    int i= key.threadLocalHashCode&(table.length-1);  
  3.     Entry e= table[i];  
  4.    if(e!=null&& e.get()== key)  
  5.        return e;  
  6.    else  
  7.        return getEntryAfterMiss(key, i, e);  
  8. }  

澳门新葡萄京娱乐场 34

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);
}

getEnrty方法只会处理key被直接命中的entry,没有直接命中的(key冲突的)数据将调用getEntryAfterMiss()方法返回对应enrty,按照源码解释,这样做是为了尽可能提升直接命中的性能。

ThreadLocalMap之getEntry的流程:

1、计算Entry数组的index((length – 1) & key.hash)。

索引计算和HashMap的异同:

①相似之处:计算方式相同,均为(length – 1) &
key.hash;length均为底层结构的大小(是大小,不是实际size)。

②不同之处:HashMap(JDK8)底层数据结构是位桶+链表/红黑树,而ThreadLocalMap底层数据结构是Entry数组;HashMap的key.hash的计算方式是native、异或、无符号位移,(key == null)
? 0 : (h = key.hashCode()) ^ (h >>>
16),ThreadLocalMap的key.hash从ThreadLocal实例化时便由nextHashCode()确定。

2、获取对应index的节点Entry;

3、如果返回节点entry
有值且其key未冲突(只有1个即entry返回的key等于传入的key),则直接返回该entry;

4、返回entry为空或键冲突,则调用getEntryAfterMiss(ThreadLocal<?>
key, int i, Entry e)方法返回entry。

b、getEntryAfterMiss源码分析:

getEntryAfterMiss处理那些getEntry时没有被命中的key(value为空的直接返回null,so更确切的说是命中且有冲突的key)。入参是当前ThreadLocal,key在数组的索引index,以及index对应的键值对。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 35澳门新葡萄京娱乐场 36

  1. private Entry getEntryAfterMiss(ThreadLocal<?> key,int i, Entry e){  
  2.     Entry[] tab = table;  
  3.    int len= tab.length;  
  4.     while(e!=null){  
  5.         ThreadLocal<?> k = e.get();  
  6.        if(k== key)  
  7.            return e;  
  8.        if(k==null)  
  9.             expungeStaleEntry(i);  
  10.        else  
  11.             i= nextIndex(i, len);  
  12.         e= tab[i];  
  13.    }  
  14.    return null;  
  15. }  

澳门新葡萄京娱乐场 37

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;
}

ThreadLocalMap之getEntryAfterMiss的流程:

仅分析Entry不为空的情况,

1、获取entry的key;

2、如果key一致(内存地址=判断),则返回该entry;

3、如果key为null,则调用expungeStaleEntry方法擦除该entry;

4、其他情况则通过nextIndex方法获取下一个索引位置index;

5、获取新index处的entry,再死循环2/3/4,直到定位到该key返回entry或者返回null。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 38澳门新葡萄京娱乐场 39

  1. private static int nextIndex(int i,int len){  
  2.    return((i+1< len)? i+1:0); // 把索引加1即可  
  3. }  

澳门新葡萄京娱乐场 40

private static int nextIndex(int i,int len){
   return((i+1< len)? i+1:0); // 把索引加1即可
}

c、expungeStaleEntry源码分析:

只要key为null均会被擦除,使得对应value没有被引用,方便回收。

[java] view
plain
copy

print?澳门新葡萄京娱乐场 41澳门新葡萄京娱乐场 42

  1. private int expungeStaleEntry(int staleSlot){  
  2.     Entry[] tab = table;  
  3.    int len= tab.length;  
  4.     // expunge entry at staleSlot  
  5.     tab[staleSlot].value=null; // 擦除当前index处value  
  6.     tab[staleSlot]=null; // 擦除当前index处key  
  7.     size–;  
  8.     // Rehash until we encounter null  
  9.     Entry e;  
  10.    int i;  
  11.    for(i= nextIndex(staleSlot, len); // 计算下一个index  
  12.         (e= tab[i])!=null; // 新index处entry不为空  
  13.          i= nextIndex(i, len)){ // 计算下一个index  
  14.         ThreadLocal<?> k = e.get(); // 获取新key(ThreadLocal)  
  15.        if(k==null){ // key为null,再次置空  
  16.             e.value=null;  
  17.             tab[i]=null;  
  18.             size–;  
  19.        }else{  
  20.            int h= k.threadLocalHashCode&(len-1); // 计算新index  
  21.            if(h!= i){ // index若未变化,说明没有多余的entry了  
  22.                 tab[i]=null;  
  23.                 // Unlike Knuth 6.4 Algorithm R, we must scan until  
  24.                // null because multiple entries could have been stale.  
  25. // 一直扫到最后一个非空位置,将其值置为碰撞处第一个entry。  
  26.                while(tab[h]!=null)  
  27.                     h= nextIndex(h, len);  
  28.                 tab[h]= e;  
  29.            }  
  30.        }  
  31.    }  
  32.    return i;  
  33. }  

澳门新葡萄京娱乐场 43

private int expungeStaleEntry(int staleSlot){
    Entry[] tab = table;
   int len= tab.length;
    // expunge entry at staleSlot
    tab[staleSlot].value=null; // 擦除当前index处value
    tab[staleSlot]=null; // 擦除当前index处key
    size--;
    // Rehash until we encounter null
    Entry e;
   int i;
   for(i= nextIndex(staleSlot, len); // 计算下一个index
        (e= tab[i])!=null; // 新index处entry不为空
         i= nextIndex(i, len)){ // 计算下一个index
        ThreadLocal<?> k = e.get(); // 获取新key(ThreadLocal)
       if(k==null){ // key为null,再次置空
            e.value=null;
            tab[i]=null;
            size--;
       }else{
           int h= k.threadLocalHashCode&(len-1); // 计算新index
           if(h!= i){ // index若未变化,说明没有多余的entry了
                tab[i]=null;
                // Unlike Knuth 6.4 Algorithm R, we must scan until
               // null because multiple entries could have been stale.
// 一直扫到最后一个非空位置,将其值置为碰撞处第一个entry。
               while(tab[h]!=null)
                    h= nextIndex(h, len);
                tab[h]= e;
           }
       }
   }
   return i;
}

5.2、set(ThreadLocal<?> key, Object
value)

[java] view
plain
copy

print?澳门新葡萄京娱乐场 44澳门新葡萄京娱乐场 45

  1. private void set(ThreadLocal<?> key, Object value){  
  2.     // We don’t use a fast path as with get() because it is at  
  3.    // least as common to use set() to create new entries as  
  4.    // it is to replace existing ones, in which case, a fast  
  5.    // path would fail more often than not.  
  6.     Entry[] tab = table;  
  7.    int len= tab.length;  
  8.    int i= key.threadLocalHashCode&(len-1);  
  9.     for(Entry e= tab[i]; e !=null;e= tab[i= nextIndex(i, len)]){  
  10. // 当前index处已有entry  
  11.         ThreadLocal<?> k = e.get();  
  12.         if(k== key){ // key(ThreadLocal)相同,更新value  
  13.             e.value= value;  
  14.            return;  
  15.        }  
  16.         if(k==null){ // 出现过期数据  
  17. // 遍历清洗过期数据并在index处插入新数据,其他数据后移  
  18.             replaceStaleEntry(key, value, i);  
  19.            return;  
  20.        }  
  21.    }  
  22.     tab[i]=new Entry(key, value);  
  23.    int sz=++size;  
  24. // 没有过期数据被清理且实际size超过扩容阈值  
  25.    if(!cleanSomeSlots(i, sz)&& sz>= threshold)  
  26.         rehash();  
  27. }  

澳门新葡萄京娱乐场 46

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;
   int i= key.threadLocalHashCode&(len-1);
    for(Entry e= tab[i]; e !=null;e= tab[i= nextIndex(i, len)]){
// 当前index处已有entry
        ThreadLocal<?> k = e.get();
        if(k== key){ // key(ThreadLocal)相同,更新value
            e.value= value;
           return;
       }
        if(k==null){ // 出现过期数据
// 遍历清洗过期数据并在index处插入新数据,其他数据后移
            replaceStaleEntry(key, value, i);
           return;
       }
   }
    tab[i]=new Entry(key, value);
   int sz=++size;
// 没有过期数据被清理且实际size超过扩容阈值
   if(!cleanSomeSlots(i, sz)&& sz>= threshold)
        rehash();
}

rehash():

size:table的实际entry数量;扩容阈值threshold:table.lenrth(默认16)大小的2/3;

首先调用expungeStaleEntries删除所有过期数据,如果清理数据后size>=threshold的3/4,则2倍扩容。

ps:阈yù值又叫临界值,是指一个效应能够产生的最低值或最高值。阀fá
控制、开关、把持。

ThreadLocalMap和HashMap在hash冲突时的解决方案对比:

HashMap:若冲突则将新数据按链表或红黑树逻辑插入。

详见《HashMap源码分析(jdk1.8)》

put(K key, V
value)的逻辑:

1、判断键值对数组tab[]是否为空或为null,是则resize(); 

2、根据键值key的hashCode()计算hash值得到当前Node的索引i(bucketIndex),如果tab[i]==null【没碰撞】,直接新建节点添加,否则【碰撞】转入3 

3、判断当前数组中处理hash冲突的方式为红黑树还是链表(check第一个节点类型即可),分别处理。【①是红黑树则按红黑树逻辑插入;②是链表,则遍历链表,看是否有key相同的节点;③有则更新value值,没有则新建节点,此时若链表数量大于阀值8【9个】,则调用treeifyBin方法(此方法先判断table是否为null或tab.length小于64,是则执行resize操作,否则才将链表改为红黑树)。】

4、如果size+1>
threshold则resize。

ThreadLocalMap:

1、若指定位置index已有数据entry,逐个遍历entry:

1.1、若index处key相同,则更新value;

1.2、若index处key为null,则调用replaceStaleEntry清理过期数据并插入新数据(从index处挨个遍历,直到找到相同key更新value结束,或者一直未找到,则在index处放入new
Entry)。replaceStaleEntry遍历时会将entry逐个后移,也就是说set进去的最新entry一定会放在index处,方便get时直接命中。

2、index处无数据,则放入新entry;随后清理过期数据并判断是否2倍扩容(size>=threshold的3/4)。

参考资料:

有任何问题,欢迎指正探讨。

一、什么是ThreadLocal?

       
根据JDK源码中的注释翻译过来就是:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或者set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private
static类型的,用于关联线程和线程的上下文。通俗地说:ThreadLocal为每一个线程维护了一份自己的变量,在自己线程内传递,不受其他线程影响。

实践

我们首先来看一段代码:

import java.util.HashMap;

import java.util.Map;



public class TreadLocalTest {

// static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){

// @Override

// protected HashMap initialValue() {

// System.out.println(Thread.currentThread().getName()+”initialValue”);

// return new HashMap();

// }

// };



public static class T1 implements Runnable {

private final static Map map = new HashMap();

int id;



public T1(int id) {

this.id = id;

}

public void run() {

// Map map = threadLocal.get();

for (int i = 0; i < 20; i++) {

map.put(i, i + id * 100);

try {

Thread.sleep(100);

} catch (Exception ex) {

}

}

System.out.println(Thread.currentThread().getName()

+ “# map.size()=” + map.size() + ” # ” + map);

}

}

public static void main(String[] args) {

Thread[] runs = new Thread[15];

T1 t = new T1(1);

for (int i = 0; i < runs.length; i++) {

runs[i] = new Thread(t);

}

for (int i = 0; i < runs.length; i++) {

runs[i].start();

}

}

}

这段程序的本意是,启动15个线程,线程向map中写入20个整型值,然后输出map。运行该程序,观察结果,我们会发现,map中压根就不止20个元素,这说明程序产生了线程安全问题。

我们都知道HashMap是非线程安全的,程序启动了15个线程,他们共享了同一个map,15个线程都往map写对象,这势必引起线程安全问题。

我们有两种方法解决这个问题:

  1. 将map的声明放到run方法中,这样map就成了方法内部变量,每个线程都有一份new
    HashMap(),无论多少个线程执行run方法,都不会有线程安全问题。这个方法也正如应用场景中提到的,如果有多处地方使用到map,传值是个烦人的地方。
  2. 将HashMap换成Hashtable。用线程同步来解决问题,然而我们的程序只是想向一个map中写入20个整型的KEY-VALUE而已,并不需要线程同步,同步势必影响性能,得不偿失。
  3. ThreadLocal提供另外一种解决方案,即在解决方案a上边,将new
    HashMap()得到的实例变量,绑定到当前线程中。之后从任何地方,都可以通过ThreadLocal获取到该变量。将程序中的注释代码恢复,再将
    private final static Map map = new
    HashMap();注释掉,运行程序,结果就是我们想要的。

二、为什么要用ThreadLocal?

       
在当今的互联网高并发的时代,同一个请求在一秒钟之内可能会发出成千上万次甚至更多,如此之大的并发亮,服务器在维护变量的必须要考虑线程安全问题,如果使用同步锁来保证一个变量在同一时间只能被一个线程访问,那么在高并发量的环境下,服务器性能必然不能达到要求。此时就需要用到ThreadLocal来为每一个线程去维护独立的变量。

实现原理

程序调用了get()方法,我们来看一下该方法的源码:

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}

getMap方法的源码:

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

该方法返回的是当前线程中的ThreadLocalMap实例。阅读Thread的源码我们发现Thread中有如下变量声明:

/* ThreadLocal values pertaining to this thread. This map is maintained

* by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

我们暂时可以将ThreadLocalMap理解为一个类似Map的这么个类,之后再讲解它。

get()方法的大致意思就是从当前线程中拿到ThreadLocalMap的实例threadLocals,如果threadLocals不为空,那么就以当前ThreadLocal实例为KEY从threadLocals中拿到对应的VALUE。如果不为空,那么就调用
setInitialValue()方法初始化threadLocals,最终返回的是initialValue()方法的返回值。下面是
setInitialValue()方法的源码

private T setInitialValue() {

T value = initialValue();

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

return value;

}

我们看到map.set(this,
value);这句代码将ThreadLocalMap的实例作为KEY,将initialValue()的返回值作为VALUE,set到了threadLocals中。

程序在声明ThreadLocal实例的时候覆写了initialValue(),返回了VALUE,当然我们可以直接调用set(T
t)方法来设置VALUE。下面是set(T t)方法的源码:

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

我们看到它比setInitialValue()方法就少了个return语句。这两种方式都能达到初始化ThreadLocalMap实例的效果。

我们再来看一下ThreadLocal类的结构。

ThreadLocal类只有三个属性,如下:

/*ThreadLocal的hash值,map用它来存储值*/

private final int threadLocalHashCode = nextHashCode();

/*改类能以原子的方式更新int值,这里主要是在产生新的ThreadLocal实例时用来产生一个新的hash值,map用该值来存储对象*/

private static AtomicInteger nextHashCode =

new AtomicInteger();

/*该变量标识每次产生新的ThreadLocal实例时,hash值的增量*/

private static final int HASH_INCREMENT = 0x61c88647;

剩下的就是一些方法。最关键的地方就是ThreadLocal定义了一个静态内部类ThreadLocalMap。我们在下一章节再来分析这个类。从ThreadLocal的类结构,我们可以看到,实际上问题的关键先生是ThreadLocalMap,ThreadLocal只是提供了管理的功能,我们也可以说ThreadLocal只是代理了ThreadLocalMap而已。

三、ThreadLocal的用法

举一个栗子

      private static final ThreadLocal<Boolean> flag = new
ThreadLocal<Boolean>()

{

@Override

protected Boolean initialValue()

{

return false;

}

};

在定义变量的时候建议重写初始化方法,设置变量初始值。

flag.set(true);

调用其set()方法设置变量值。

flag.get();

调用其get()方法过去变量副本的值。

flag.remove();

调用其remove()方法,清空变量副本,防止多线程访问导致逻辑错误。

ThreadLocalMap源码分析

既然ThreadLocalMap实现了类似map的功能,那我们首先来看看它的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;

int i = key.threadLocalHashCode & (len-1);



for (Entry e = tab[i];

e != null;

e = tab[i = nextIndex(i, len)]) {

ThreadLocal k = e.get();



if (k == key) {

e.value = value;

return;

}



if (k == null) {

replaceStaleEntry(key, value, i);

return;

}

}



tab[i] = new Entry(key, value);

int sz = ++size;

if (!cleanSomeSlots(i, sz) && sz >= threshold)

rehash();

}

这个方法的主要功能就是讲KEY-VALUE存储到ThreadLocalMap中,这里至少我们看到KEY实际上是
key.threadLocalHashCode,ThreadLocalMap同样维护着Entry数组,这个Entry我们在下一节会讲解。这里涉及到了Hash冲突的处理,这里并不会向HashMap一样冲突了以链表的形式往后添加。如果对这个Hash冲突解决方案有兴趣,可以再进一步研究源码。

既然ThreadLocalMap也是用Entry来存储对象,那我们来看看Entry类的声明,Entry被定义在ThreadLocalMap的内部:

static class Entry extends WeakReference<ThreadLocal> {

/** The value associated with this ThreadLocal. */

Object value;



Entry(ThreadLocal k, Object v) {

super(k);

value = v;

}

}

这里我们看到Entry集成了WeakReference类,泛型声明了ThreadLocal,即每一个Entry对象都保留了对
ThreadLocal实例的弱引用,之所以这么干的原因是,线程在结束之后需要将ThreadLocal实例从map中remove调,以便回收内存空间。

四、ThreadLocal的基本原理

ThreadLocal源码分析:

initialValue函数用来设置ThreadLocal的初始值

protected T initialValue() {

        return null;

    }

ThreadLocal的get方法:

public T get() {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null) {

            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null) {

                @SuppressWarnings(“unchecked”)

                T result = (T)e.value;

                return result;

            }

        }

        return setInitialValue();

    }

根据当前线程获取到当前线程维护的map,然后从map中获取对应的变量值,如果为空就调用设置初始值方法进行初始化。

ThreadLocal的setInitialValue方法:

private T setInitialValue() {

        T value = initialValue();

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

        return value;

    }

setInitialValue()方法,先调用初始化方法得到变量的初始值,然后存入map中,如果map为空直接创建一个map,并将变量和其初始值保存。

ThreadLocal的createMap方法:

void createMap(Thread t, T firstValue) {

        t.threadLocals = new ThreadLocalMap(this, firstValue);

    }

创建map方法很简单,在此不多解释。

ThreadLocal的set方法:

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

同get()方法一样先拿到map,然后将变量的值进行保存,如果map为空就调用上面的createMap(t,
value)方法。

ThreadLocal的remove()方法:

public void remove() {

        ThreadLocalMap m = getMap(Thread.currentThread());

        if (m != null)

            m.remove(this);

    }

先获取map,然后将key所对应的键值对移除。

ThreadLocalMap是使用ThreadLocal的弱引用作为Key的一个map,大致源码如下:

static class ThreadLocalMap {

        /**

        * The entries in this hash map extend WeakReference, using

        * its main ref field as the key (which is always a

        * ThreadLocal object).  Note that null keys (i.e. entry.get()

        * == null) mean that the key is no longer referenced, so the

        * entry can be expunged from table.  Such entries are referred
to

        * as “stale entries” in the code that follows.

        */

        static class Entry extends
WeakReference<ThreadLocal<?>> {

            /** The value associated with this ThreadLocal. */

            Object value;

            Entry(ThreadLocal<?> k, Object v) {

                super(k);

                value = v;

            }

        }

}

ThreadLocalMap是Thread类维护的一个map。key是ThreadLocal变量,value是值

以下是引用关系图,实线表示强引用,虚线表示弱引用:

澳门新葡萄京娱乐场 47

如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,但是这个时候没有必要担心会造成内存泄漏,在JDK的ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施,下面是ThreadLocalMap的getEntry方法的源码:

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);

        }

getEntryAfterMiss函数的源码:

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;

        }

expungeStaleEntry函数的源码:

private int expungeStaleEntry(int staleSlot) {

            Entry[] tab = table;

            int len = tab.length;

            // expunge entry at staleSlot

            tab[staleSlot].value = null;

            tab[staleSlot] = null;

            size–;

            // Rehash until we encounter null

            Entry e;

            int i;

            for (i = nextIndex(staleSlot, len);

                (e = tab[i]) != null;

                i = nextIndex(i, len)) {

                ThreadLocal<?> k = e.get();

                if (k == null) {

                    e.value = null;

                    tab[i] = null;

                    size–;

                } else {

                    int h = k.threadLocalHashCode & (len – 1);

                    if (h != i) {

                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan
until

                        // null because multiple entries could have been
stale.

                        while (tab[h] != null)

                            h = nextIndex(h, len);

                        tab[h] = e;

                    }

                }

            }

            return i;

        }

整理一下ThreadLocalMap的getEntry函数的流程:

  1. 首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode &
    (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;

2.
如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询。

     
在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收,所以并不会发生内存溢出,但是为了程序效率以及安全性,我们建议最好手动点用remove()方法去释放内存。

总结

首先,ThreadLocalMap并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果,实际上自己在方法中new出来变量也能达到类似的效果。ThreadLocalMap跟线程安全基本不搭边,绑定上去的实例也不是多线程公用的,而是每个线程new一份,这个实例肯定不是共用的,如果共用了,那就会引发线程安全问题。ThreadLocalMap最大的用处就是用来把实例变量共享成全局变量,在程序的任何方法中都可以访问到该实例变量而已。网上很多人说ThreadLocalMap是解决了线程安全问题,其实是望文生义,两者不是同类问题。

发表评论

电子邮件地址不会被公开。 必填项已用*标注