threadLocal源码分析(1)

threadLocal能够保证变量在每个线程中单独存在,各个线程互相不会影响。

通常我们这么写threadLocal变量。

private ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue() {
return 0;
}
};


这给我们造成一种误解,认为线程的变量信息都存在在了ThreadLocal类中。其实不是酱紫的。

---ThreadLocal
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;}
   
    --Thread    
    ThreadLocal.ThreadLocalMap threadLocals = null;



从ThreadLcoal的getMap方法可以看出这个维护了线程区分的变量,其实是放在了线程类Thread当中。

大体描述:线程类包含一个ThradLocalMap变量,这样如果有三个线程就有三个ThrdLocalMap.ThrdLocalMap类中包含一个Entry数组Table[Entry],而Entry类型也是ThreadLcoal,只不过是弱引用WeakReference。线程变量放置原理就是先通过hash获得位置,如果该位置有数据就往后继续找。如果找到与其key相同的Entry,则更新value。如果找到空位置,则创建Entry,把数据放置进去。

代码:

1、初始化变量过程、获取ThreadLocal类的变量

初始化过程是在线程get数据的时候才走的,代码如下。

public T get() {
//获取当前线程
    Thread t = Thread.currentThread();
//获取当前线程的threadLocals    ThreadLocalMap map = getMap(t);
    if (map != null) {
    //根据当前变量在当前线程的hashCode或得到该Entry信息
        ThreadLocalMap.Entry e = map.getEntry(this);
        //如果获得不为空,则返回值
        if (e != null)
            return (T)e.value;
    }
    //如果获得为空,则走初始化方法获得首个变量的值。
    return setInitialValue();
}

2、设置ThreadLocal类的变量

public void set(T value) {
//获得当前线程
    Thread t = Thread.currentThread();
    //获得当前线程的ThreadLocalMap变量
    ThreadLocalMap map = getMap(t);
    //如果或得到,则更新该值为新值。(1)
    if (map != null)
        map.set(this, value);
    else
    //如果没有或得到,则在map中创建新的Entry放置该值。(2)
        createMap(t, value);
}


(1)、(2)的实现代码如下:
(1)

private void set(ThreadLocal key, Object value) {
//注释中强调没有通过get(key)方法去获得相应的值,是因为大多数时候set方法是要新创建一个Entry。而这种情况用get方法会引入异常。//获得ThradLocalMap的所有Entry
    Entry[] tab = table;
    //获得ThradLocalMap中table 的长度
    int len = tab.length;
    //根据key的哈希code去获得该数据应该在的位置(不一定找到)
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         //获得Entry的ThradLocal信息
        ThreadLocal k = e.get();
       //如果该值和key相等,则更新该值。
        if (k == key) {
            e.value = value;
            return;
        }
       //如果改位置为空则处理过期数据        
       if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
     //在改位置新生成一个Entry实例。
        tab[i] = new Entry(key, value);//数组的长度+1
        int sz = ++size;
        
        //如果该过程没有清除过期数据的操作(1-1),并且长度大于了预制,那么会调用rehash方法(1-2)
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


(1-1):清除过期数据的操作
   

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {//从位置i开始往后走
        i = nextIndex(i, len);
        Entry e = tab[i];
        //该位置数据的值为空
        if (e != null && e.get() == null) {
            n = len;
            //清除标示为为true
            removed = true;
            /开启清除过期实体的方法(1-1-1)   
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}


(1-1-1)清除过程:

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot 
   // 清除过期位置的entry数据
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    //有数值的个数-1
    size--;
// Rehash until we encounter null
//再次hash知道我们碰到一个值为null的Entry
    Entry e;
    int i;
    //找到从位置i开始不为空的Entry 放置到e中。
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
         //获得该值的ThreadLocal信息
        ThreadLocal k = e.get();
        //如果为空,则清除
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
        //如果不为空,则获得该Entry的hashCode,获得一个位置
            int h = k.threadLocalHashCode & (len - 1);
            //如果这个位置和数据为空的位置不相等
            if (h != i) {
            //则清空数据为空的实体变量
                tab[i] = null;
            //找到h之后第一个数值为空的Entry,将e放置到该位置           
               while (tab[h] != null)
                h = nextIndex(h, len);
                tab[h] = e;
         }
     }
 }
 //返回为空的位置信息
    return i;
}

(1-2)、rehash

private void rehash() {
    expungeStaleEntries();
    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}


private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            //变量table
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                //如果找到非空实体,而值为空,则执行清除过期实体的操作。
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}


(2)、创为该线程生成新的threadLocals变量。这个变量很有意思,他把ThradLocalMap实体为变量传给了thread的 threadLocals。这也就是巧妙之处,数据放置到了

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
//初始化一个table
    table = new Entry[INITIAL_CAPACITY];
    //根据该ThradLocalMap实体的hashCode获得位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //生成基于ThradLocalMap实体的hashCode为key、firstValue为值的Entry信息。
    table[i] = new Entry(firstKey, firstValue);
    size = 1;    setThreshold(INITIAL_CAPACITY);
}

//总容量的三分之二为阈值。

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

最后附上一个简单的图,供大家理解下。

143135_eE5X_166980.jpg



测试代码也贴上来

/**
 * Created by user on 2015/12/17.
 */
public class TestClient extends Thread {
    private ThreadLocalTest sn;
    public TestClient(ThreadLocalTest sn,String name ) {
        super(name);
        this.sn = sn;
    }
    public void run() {
        for (int i = 0; i < 30; i++) {
            // 每个线程打出3个序列值
            System.out.println("thread[" + Thread.currentThread().getName()
                    + "] sn[" + sn.getNextNum() + "]");
            System.out.println("thread[" + Thread.currentThread().getName()
                    + "] sn[" + sn.getNextStr() + "]");
            System.out.println("thread[" + Thread.currentThread().getName()
                    + "] sn[" + sn.getIntNextValue() + "]");
        }
    }
}
/**
 * Created by user on 2015/12/17.
 */
public class ThreadLocalTest {
    private ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
        public Integer initialValue() {
            return 0;
        }
    };
    private ThreadLocal<String> seqString = new ThreadLocal<String>(){
        public String initialValue() {
            return "app";
        }
    };
    private ThreadLocal<Double> seqDouble = new ThreadLocal<Double>(){
        public Double initialValue() {
            return 1.0;
        }
    };
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }
    public String getNextStr(){
        seqString.set(seqString.get()+"A");
        return seqString.get();
    }
    public Double getIntNextValue(){
        seqDouble.set(seqDouble.get()+2.0);
        return seqDouble.get();
    }
    public static void main(String[] args){
        ThreadLocalTest sn = new ThreadLocalTest();
        
        TestClient t1 = new TestClient(sn,"线程1");
        TestClient t2 = new TestClient(sn,"线程2");
        TestClient t3 = new TestClient(sn,"线程3");
        TestClient t5 = new TestClient(sn,"线程5");
        TestClient t6 = new TestClient(sn,"线程6");
        t1.start();
        t2.start();
        t3.start();
        t5.start();
        t6.start();
    }
}

转载于:https://my.oschina.net/zjItLife/blog/546771

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值