4-6 ThreadLocal

一、什么是ThreadLocal

java.lang.ThreadLocal,线程本地变量,也叫线程局部变量。。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种线程隔离机制。对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。
实际开发中真正使用ThreadLocal的场景较少,大多数使用都是在框架里面。最常见的使用场景可以用它来解决数据库连接、Session管理等保证每一个线程中使用的数据库连接是同一个。
二、API简单使用

public class ThreadLocalMain {

    /**
     * 根据Java Doc的建议,ThreadLocal一般声明为static变量(被static修 
    *饰生命周期延长,可以杜绝Entry继承弱引用WeakReference带来的在垃
  *圾回收时会直接删除掉key ThreadLocal对象。但是需要注意的是线程运
*行时间过长,对象过大如果不回收有可能会造成内存溢出。需要及时remove()掉.)
     * withInitial方法,初始化一个有值的threadLocal.
     */
   static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "调用withInitial初始化一个值");
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " :" + threadLocal.get());
        Runnable r1 = () -> {
            String name = Thread.currentThread().getName();
            threadLocal.set(name + "调用set方法");
            System.out.println("------>" + threadLocal.get());
        };

        Runnable r2 = () -> {
            // 如果初始化ThreadLocal时没有使用withInitial初始化初始值,那么在get()时会返回空。
            String initialVal = threadLocal.get();
            System.out.println("initialVal----------->" + initialVal);
            String name = Thread.currentThread().getName();
            // 线程thread-local-2 执行set方法.
            threadLocal.set(name + "调用set方法!");
            // thread-local-2 get值为当前线程set的值.
            System.out.println("------>" + threadLocal.get());
            // 移除ThreadLocal变量
            threadLocal.remove();
            // threadLocal.get()值为初始化值.
            System.out.println("remove:" + threadLocal.get());
        };

        Thread thread = new Thread(r1, "thread-local-1");
        Thread thread2 = new Thread(r2, "thread-local-2");
        thread.start();
        thread2.start();
    }
}
**********************************************************
main :调用withInitial初始化一个值
------>thread-local-1调用set方法
initialVal----------->调用withInitial初始化一个值
------>thread-local-2调用set方法!
remove:调用withInitial初始化一个值    

三、API源码解析

ThreadLocal API
public T get() {
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 //获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null) {
 // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
 ThreadLocalMap.Entry e = map.getEntry(this);
 // 找到对应的存储实体 e 
 if (e != null) {
 @SuppressWarnings("unchecked")
 // 获取存储实体 e 对应的 value值
 // 即为我们想要的当前线程对应此ThreadLocal的值
 T result = (T)e.value;
 return result;
 }
 }
 // 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象
 // 调用setInitialValue进行初始化
 return setInitialValue();①
 }
 /**
 * 获取当前线程Thread对应维护的ThreadLocalMap 
 * 
 * @param  t the current thread 当前线程
 * @return the map 对应维护的ThreadLocalMap 
 */
 ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
 }


 private T setInitialValue() {
 // 调用initialValue获取初始化的值
 T value = initialValue();
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 // 获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null)
 // 存在则调用map.set设置此实体entry
 map.set(this, value);
 else
 // 1)当前线程Thread 不存在ThreadLocalMap对象
 // 2)则调用createMap进行ThreadLocalMap对象的初始化
 // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
 createMap(t, value);
 return value;
 }

 protected T initialValue() {
 return null;
 }

 /**
 *创建当前线程Thread对应维护的ThreadLocalMap 
 *
 * @param t 当前线程
 * @param firstValue 存放到map中第一个entry的值
 */
 void createMap(Thread t, T firstValue) {
 //这里的this是调用此方法的threadLocal
 t.threadLocals = new ThreadLocalMap(this, firstValue);
 }
/**
 * 设置当前线程对应的ThreadLocal的值
 *
 * @param value 将要保存在当前线程对应的ThreadLocal的值
 */
 public void set(T value) {
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 // 获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null)
 // 存在则调用map.set设置此实体entry
 map.set(this, value);
 else
 // 1)当前线程Thread 不存在ThreadLocalMap对象
 // 2)则调用createMap进行ThreadLocalMap对象的初始化
 // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
 createMap(t, value);
 }

 /** ThreadLocal的set最终调用了ThreadLocalMap的set方法 **/
 private void set(ThreadLocal<?> key, Object value) {

 Entry[] tab = table;
 int len = tab.length;
 int i = key.threadLocalHashCode & (len-1);
//哈希码和数组长度求元素放置的位置,即数组下标 从i开始往后一直遍历到数组最后一个Entry
 for (Entry e = tab[i];
 e != null;
 e = tab[i = nextIndex(i, len)]) {
 ThreadLocal<?> k = e.get();
 //如果key相等,覆盖value
 if (k == key) {
 e.value = value;
 return;
 }
//如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
 if (k == null) {
 replaceStaleEntry(key, value, i);
 return;
 }
 }

 tab[i] = new Entry(key, value);
 int sz = ++size;
 //如果超过阀值,就需要再哈希了
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
 rehash();
 }

通过源码我们知道不管是set、get、remove操作的都是ThreadLocalMap,key=ThreadLocal对象,value=线程局部变量缓存值。getMap最终调用的Thread的成员变量 ThreadLocal.ThreadLocalMap threadLocals.

public class Thread implements Runnable {
 /* 与此线程有关的 ThreadLocal 值,该映射由 ThreadLocal 类维护*/
 ThreadLocal.ThreadLocalMap threadLocals = null;

 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
public class ThreadLocal<T> {
 /**ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap是一个定制的哈希映射,
 仅适用于维护线程本地值。ThreadLocalMap类是包私有的,允许在Thread类中声明字段。为了帮助
 处理非常大且长时间的使用,哈希表entry使用了对键的弱引用。有助于GC回收。 **/
 static class ThreadLocalMap {
 static class Entry extends WeakReference<ThreadLocal<?>> {
 /** The value associated with this ThreadLocal. */
 Object value;

 Entry(ThreadLocal<?> k, Object v) {
 super(k);
 value = v;
 }
 }
 }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容