文档章节

threadLocal源码分析(1)

hyssop
 hyssop
发布于 2015/12/17 14:31
字数 1506
阅读 70
收藏 10

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

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



测试代码也贴上来

/**
 * 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();
    }
}

© 著作权归作者所有

hyssop
粉丝 20
博文 102
码字总数 111521
作品 0
昌平
程序员
私信 提问
PerfMa给OpenJDK社区提交的第一个Patch

概述 前两天给openjdk gc-dev的email list提交了一个问题,主要是针对Full GC之后,GC日志里Metaspace的大小在GC前后都一直不变的问题,我在邮件里大概也提了下如何修复该问题,以及猜测了下...

你假笨
2018/09/25
0
0
android6.0源码分析之Camera API2.0简介

前面几篇主要分析的是android Camera API1.0的架构以及初始化流程,而google在android5.0(Lollipop)开始对Camera的架构进行了调整,为了适应HAL3,新添加实现了CameraDeviceClient,而Camer...

天王盖地虎626
05/21
20
0
SpringBoot+MyBatis+MySQL读写分离

引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做。因此,一般来讲,读写分离有两种实现方式。...

编程SHA
01/14
85
0
【死磕Sharding-jdbc】—–路由&执行

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/09efada2d086 继续以模块中的为基础,剖析分库分表简单查询SQL实现--,即如何执行简单的查询SQL,接下来的分析以执行SQL语句为例...

飞哥-Javaer
2018/05/03
0
0
sharding-jdbc源码分析—准备工作

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/7831817c1da8 接下来对sharding-jdbc源码的分析基于tag为源码,根据sharding-jdbc Features深入学习sharding-jdbc的几个主要特性...

飞哥-Javaer
2018/05/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

MongoDB系列-解决面试中可能遇到的MongoDB复制集(replica set)问题

关注我,可以获取最新知识、经典面试题以及微服务技术分享   MongoDB复制集(replica set):MongoDB复制集维护相同数据集的一组mongod进程,复制集是生产部署的基础,具有数据冗余以及高可用...

ccww_
25分钟前
2
0
SpringBoot系列:Spring Boot集成Spring Cache,使用RedisCache

前面的章节,讲解了Spring Boot集成Spring Cache,Spring Cache已经完成了多种Cache的实现,包括EhCache、RedisCache、ConcurrentMapCache等。 这一节我们来看看Spring Cache使用RedisCache。...

杨小格子
34分钟前
2
0
OpenJDK之CountDownLatch

OpenJDK8,本人看的是openJDK。以前就看过,只是经常忘记,所以记录下 图1 CountDownLatch是Doug Lea在JDK1.5中引入的,作用就不详细描述了, await()方法,如果还有线程在执行,那么当前线程...

克虏伯
40分钟前
3
0
简单编程

1.编写一个程序,提示用户输入名和姓,然后以“名,姓”的格式打印出来。 #include<stdio.h>int main(){char name[3];char family[3];printf("Please input your name and family:\n...

电子工程197沈志初
44分钟前
4
0
详解Mysql分布式事务XA(跨数据库事务)

在开发中,为了降低单点压力,通常会根据业务情况进行分表分库,将表分布在不同的库中(库可能分布在不同的机器上)。在这种场景下,事务的提交会变得相对复杂,因为多个节点(库)的存在,可...

slagga
49分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部