您的位置 首页 java

Java并发编程(十六)ThreadLocal源码分析

上一篇:

一、背景

多个线程并发访问同一个共享数据的时候,并发修改同一个数据的时候,可能会导致数据错乱,必须要加一些并发同步机制;

如果每个 线程 拷贝一个线程自己本地的变量副本,每个线程就直接操作自己的本地副本就ok了,然后就跟其他的线程就没有冲突了,避免多个线程并发的访问同一个共享的数据;

二、demo

 public  static   void  main(String[] args) {
     ThreadLocal <String> name = new  thread Local<String>();
    ThreadLocal<String> age = new ThreadLocal<String>();

    new Thread(){
        @Override
        public void run() {
            name.set("张三");
            age.set("18");
            System.out.println("线程1取name值:"+name.get());
            System.out.println("线程1取age值:"+age.get());
        }
    }.start();

    new Thread(){
        @Override
        public void run() {
            name.set("李四");
            age.set("22");
            System.out.println("线程2取name值:"+name.get());
        }
    }.start();
}  

三、源码分析

ThreadLocal在构造的时候就会根据next hashCode 方法算出一个 HashCode 出来;

Java并发编程(十六)ThreadLocal源码分析

因为static的原因,在每次new ThreadLocal时因为threadLocal hash Code的初始化,会使 Thread LocalHashCode值自增一次,增量为0x61c88647。

0x61c88647是 斐波那契 散列乘数,它的优点是通过它散列( Hash )出来的结果分布会比较均匀,可以很大程度上避免hash冲突,已初始容量16为例,hash并与15位运算计算数组下标结果如下:

hashCode

数组下标

0x61c88647

7

0xc3910c8e

14

0x255992d5

5

0x8722191c

12

0xe8ea9f63

3

0x4ab325aa

10

0xac7babf1

1

0xe443238

8

0x700cb87f

15

set方法

 public void set(T value) {
  	/**获取当前线程的ThreadLocalMap */
    Thread t = Thread.currentThread();
  	/** 获取当前线程内部的ThreadLocalMap */
    ThreadLocalMap map = getMap(t);
  	/** 如果不为空则进行赋值,如果为空则创建 */
    if (map != null)
      	/** key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值 */
        map.set(this, value);
    else
        createMap(t, value);
}  
 ThreadLocal map  getMap(Thread t) {
    return t.threadLocals;
}  

如果为空创建ThreadLocalMap

 void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}  
 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  	/** INITIAL_CAPACITY:16
    	* 创建一个大小为16的数组
      */
    table = new Entry[INITIAL_CAPACITY];
  	/** 根据key的Hash对数组下标取模之后放入对应数组下标中去
    	* 这里的threadLocalHashCode就是在ThreadLocal初始化时算出的Hash
      */
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
  	/** 设置扩容 阈值  */
    setThreshold(INITIAL_CAPACITY);
}  

  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.
		/** 获取线程数组,根据key的Hash对数组长度-1取模算一个下标 */
    Entry[] tab = table;
    int len = tab.length;
  	/** 这里的threadLocalHashCode就是在ThreadLocal初始化时算出的Hash */
    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;
        }
    }
		/** 数组元素为空,直接创建Entry进行添加 */
    tab[i] = new Entry(key, value);
    int sz = ++size;
  	/** 满足条件数组扩容x2 */
     if  (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}  

get方法

 public T get() {
  	/** 获取当前线程map */
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
  	/** 如果map不为空*/
    if (map != null) {
      	/** 将当前ThreadLocal作为key进行查询 */
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
  	/** 如果找不到元素则调用setInitialValue返回默认值,默认值:null */
    return setInitialValue();
}  
 private Entry getEntry(ThreadLocal<?> key) {
  	/** 根据之前算出的Hash获取下标元素 */
    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);
}  

setInitialValue方法

 private T setInitialValue() {
  	/** 获取值null */
    T value = initialValue();
  	/** 获取当前线程的map,设置默认值null入map */
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
  	/** 返回默认值 */
    return value;
}  
 protected T initialValue() {
    return null;
}  

remove方法

 public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
      	/** 如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量 */
        m.remove(this);
}  
 private void remove(ThreadLocal<?> key) {
    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)]) {
      	/** 如果key相等则调用clear清空 */
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}  

注意事项

每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个 HashMap ),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

文章来源:智云一二三科技

文章标题:Java并发编程(十六)ThreadLocal源码分析

文章地址:https://www.zhihuclub.com/195620.shtml

关于作者: 智云科技

热门文章

网站地图