`
greemranqq
  • 浏览: 980017 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论
阅读更多

一、序言

       这里会分析ThreadLocal 源码以及原理,和它的正确使用原则,还有我们应用过的地方,帮助大家更深刻的理解这个类的使用。

       ThreadLocal  在JDK1.2的版本的就提供的一个类,它提供了一种新的思路去解决多线程问题,同时ThreadLocal  不是线程类,仅仅是一个线程的变量副本,他是如何来实现这个功能的呢,我们从源码进行分析。

 

 

二、源码分析

       

// 仅仅是一个单独的类,没有除Object外的其他父类
public class ThreadLocal<T> {...}

   

 

    我们还是从基本的API 方法分析,其中包括get(),initialValue(),remove(),set(T value) 方法:

   2.1  get() : 返回此线程局部变量的当前线程副本中的值

 

   

  public T get() {
        // 回先获得当前线程
        Thread t = Thread.currentThread();
        // 然后获得线程t 里面的变量threadLocals,看下面的方法getMap()
        // 这里变量其实是个map,具体实现,我们先分析几本原理,再分析实现
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 然后获得当前里面的值,这里可以参考hashMap 的实现原理
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        // 如果没有获得值,表示还没set 值,就会返回初始值
        // 这里是初始化ThreadLocal 的方法。
        return setInitialValue();
    }
  // 注意:这里是从Thread 获得的变量
  // 关于ThreadLocalMap是个什么东西,我们先介绍几本原理,在详细分析。
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

   2.2 initialValue()  返回此线程局部变量的当前线程的“初始值”。

    这是源码,我们可以这样使用

 protected T initialValue() {
        return null;
    }

   

// 这是API 介绍的,我们可以按类似的方式给它赋初始值
// 可以看出这个是protected 的initialValue 准备让子类重写的
// 这个值仅仅作为初始化用,当第一次执行set 方法的时候,就会覆盖这个值,但是初始值始终存在。
// 让你remove 的之后,在次进行get 就会返回初始值
private static final ThreadLocal <Integer> uniqueNum = 
        new ThreadLocal<Integer> () {
            @Override 
            protected Integer initialValue() {
                return 1;
        }
    };

 

 

   2.3 set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。

 

    

public void set(T value) {
        // 同样是获得当线程,和里面的变量map.然后set 值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            
            createMap(t, value);
    }

// 这里变量第一次为 null 的情况,会创建一个新对象。
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

 

 

    

// 可以看出这是ThreadLocal 的内部类 
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;
            }
        }

}

 

 

    2.4 remove() 移除此线程局部变量当前线程的值。

   

public void remove() {
         // 这里也是传入当前线程,然后操作变量map 
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

 

 

    结论:从上面的基本方法可以看出,我们都是获得Thread.currentThread(),然后操作里面的一个ThreadLocalMap类型的变量完成了保存对象的任务,这也就完成了和线程之间的绑定,至于为什么是线程的一个变量呢,我这里再帖一下Thread的源码。

   

public class Thread implements Runnable {
 /* ThreadLocal values pertaining to this thread. This map is maintained
  *  by the ThreadLocal class. */
 // 可以看出Thread 类,对 ThreadLocalMap 的引用。
 ThreadLocal.ThreadLocalMap threadLocals = null;

}

    那么我们现在看看 ThreadLocalMap 到底是干嘛的呢?

 

 

三、ThreadLocalMap  源码分析

        

// 这是ThreadLocal的一个内部类
static class ThreadLocalMap {
        // 这又是一个内部类,继承与弱引用,至于弱引用这里暂时不详细介绍
        // 可以参考JVM 里面的的引用类型 和 GC回收机制
        // 看过hashMap 源码的人,肯定比较熟悉这种写法
        // 实现的是一种K,V 的内部类机制
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
}

 

 

   3.1 构造函数

   

 // 这里参考hashMap 的方式设计,就不多多介绍了
 // 一个放着entry  的数组table 
 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            // 这里hashCode 的取值比较特别,我们单独分析
            // 根据hashCode 和长度取余运算
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

    3.2 特殊的hashCode 值

// 这里看出,这个hashCode 值在创建ThreadLocal 对象的时候就创建了
private final int threadLocalHashCode = nextHashCode();

// 1640531527
// 这里为什么要用这个数字,我还真不清楚,但是能确定是为了更均匀的分布
// 这个和hashMap 的 hashCode 计算方式一样,应该都有进行专门的测试,以后再研究这个
private static final int HASH_INCREMENT = 0x61c88647;
 
private static AtomicInteger nextHashCode = new AtomicInteger();   
// 这里我们可以看出hashCode 居然是不停的加上HASH_INCREMENT 进行
private static int nextHashCode() {
	return nextHashCode.getAndAdd(HASH_INCREMENT); 
    }

 

    3.3 set 方法:这里是内部类如何存放值得

   

private void set(ThreadLocal key, Object value) {
            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;
                }
                // 如果存放的k为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();
        }

       

 

 

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

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

    3.5 remove 方法,删除元素

   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)]) {                
                if (e.get() == key) {
                    // 这里删除仅仅是引用设置为null 就行了。
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 

 

   结论:我们可以看出内部类的实现,是对一个继承了弱应用的的K.V entry 的操作。我们看出每一次操作都会去检测一那些对象为空了,然后进行删除。同时也看出了它的操作虽然类似map,但是却不是操作的map.关于弱引用的作用我这里简答描述是:当某对象已经不使用了,为null的情况下,为了不让强引用占有这个空间,那么弱引用能加快GC的一种手段。

 

 

四、应用场景

       4.1 描述: 

            ThreadLocal 提供一种新的思路去解决多线程问题,是解决什么问题呢?又是如何解决呢?

            在多线程并发中,我们常常遇到的问题是共享资源的操作,常用的办法是加锁机制,但是这种机制负面影响比较多。然后ThreadLocal 提供一种保存带状态的共享变量的副本的方式,来隔绝各个线程中带来的影响,以空间换时间。

 

       4.2 实例1:

             比如:Spring 中,我们常用的单利模式,假设我一个Count 类,里面有个属性num,用来观察某一时刻的浏览数量。        

 

public static class Count{
   public static  int num = 0;
}

           在多线程中,不是使用加锁等手段,如果A 线程在A时刻先将num 变成100,然后准备访问之前,这时候B线程在B时刻将num 变成200.然后在由A线程去访问,返回200,明显是有错误的。当然创建多个Count也可以解决,那么我们在不创建的多个Count 对象的情况下,如何保证安全呢?

 

           我们可以在A B线程创建一个ThreadLocal 变量,然后当A线程将num 变成100的时候,同时将这这个100的值,存放在ThreadLocal 里面,那么下次访问就从变量里面提取,就不会错误了,当然由于业务原因,这里例子不太好,但是道理类似。

 

           实例 2:这里我copy 的网上hibernate 里面Session 的例子:

            

    private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

       可以看出,每个线程保存一份session,方便下次使用,同时各自有各自的状态,同时也会随着线程的退出而快速的清理这个副本。

 

        实例3 :这里简单的原理,我直接贴下代码,大家感受一下:

         

    static class A {
		public boolean  flag = false;
	}
   
	public static void main(String[] args) throws InterruptedException {	
		ThreadLocal thread =  new ThreadLocal();
		A a = new A();		
	        thread.set(a.flag);
		a.flag = true;
                // 虽然一个线程,但是变量值是可以做个隔离的
		System.out.println(thread.get() +":"+a.flag);
	}

 

 

五、Thread 和 ThreadLocal 关系和原理:

     

    // 这里我们已经说过每个Thread 里面都有 引用,变量副本主要是通过
    // 操作内部类进行实现的
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // 这个变量作用是为可以子线程访问父线程的变量而准备的。
    // 这里我们暂时不做详细介绍,原理都差不多
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

     

    下面我再看看线程退出的时候,默认会调exit()方法:

    

   private void exit() {
	if (group != null) {
	    group.remove(this);
	    group = null;
	}
        // 这里看到, 线程退出,会将这个设置为空
        // 由前的源码分析可以看出,当key 为空的时候会被清除
        // 加快GC
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

 

  

总结:

        1.ThreadLocal我仅仅分析了大部分源码, 还有一部分没分析,感兴趣的朋友可以共同讨论

        2.强调两点:

           a.ThreadLocal不是线程,仅仅是存放变量的的一个副本,每个Thread都有引用

           b.ThreadLocal实现不是map,虽然类似!它主要用来存放有变化的状态,用于隔离多线程的影响

        3.JDK 建议创建以static final 为主,节约内存。

        4.关于使用原则,只能大家对原理机制理解了,就知道什么地方该用了

        5.上面仅限于自己理解,如果有异议的地方请指出,还有不懂,或者其他想法的朋友,希望多沟通讨论。

 

2
0
分享到:
评论
2 楼 greemranqq 2014-03-24  
washingtonDC 写道
虽然一下子不能理解,但佩服楼主的探索精神,支持一个,

这个我不是用大白话写的,因此如果以前你没用过,或者没分析过,一下子很难明白,仅仅知道如何使用就行了。这个希望能对想深入研究的朋友提供一点思路,多讨论多分析,实践,相信理解得更好。
1 楼 washingtonDC 2014-03-24  
虽然一下子不能理解,但佩服楼主的探索精神,支持一个,

相关推荐

    清华大学DeepSeek从入门到精通(视频课程+PDF)

    【清华大学】DeepSeek从入门到精通(视频课程+PDF)

    更新-“双高计划”专业群完整申报书与建设方案/中期评估报告

    自2019年以来,教育部启动实施“双高计划”,遴选确定首批“双高计划”建设单位197所,其中高水平学校建设单位56所,高水平专业群建设单位141所,河南省有黄河水利职业技术学院、河南工业职业技术学院等6所职业学校入选。2022年,教育部开展国家“双高计划”中期绩效评价,从评价结果看,国家“双高计划”任务进展顺利,建设成效突出,形成了一批先进经验做法和典型案例,在引领职业教育改革、服务国家战略和支撑区域发展方面形成示范势头。 今天,我们给大家分享一些“双高计划”专业群完整申报书与建设方案和中期评估报告。 ## 一、专业群完整申报书与建设方案 ## 二、“双高计划”中期报告 (100多份)

    电子商务平台秒杀系统减库存设计的核心逻辑与优化策略解析

    内容概要:本文详细探讨了电商平台上秒杀系统中减库存的设计逻辑和技术优化方法。首先,文中阐述了‘下单减库存’、‘付款减库存’和‘预扣库存’三种常见方式及其各自面临的问题和局限性,尤其是面对高并发流量冲击下的系统稳定性与数据准确性保障挑战。接着讨论了适用于大规模促销活动中快速而精准地扣除存货的方法,提出了诸如应用本地缓存(Local Cache)、引入高性能持久化键值存储(如Redis),甚至修改数据库引擎源代码(InnoDB 层面排队机制)等一系列先进解决方案来确保交易流程顺畅。此外,还提到了在极端情况发生(例如超卖)时如何借助补救措施挽回损失的具体实例。 适合人群:电商平台开发运维技术人员;有兴趣深入了解电商业务架构和技术优化的开发者和IT管理人员。 使用场景及目标:①帮助设计师理解不同减库存策略的应用时机及其利弊;②指导程序员针对特定业务需求选择最适合的技术路径进行项目构建;③提供给运维专家关于改善在线交易平台响应速度和服务质量的专业见解。 其他说明:本篇文章对于构建高效的电子商贸系统有着极高的参考价值,尤其是那些准备应对瞬息万变市场环境下的企业来说尤为重要。它不仅限于理论探讨层面,

    动态表单,VUE动态表单 基于vue+elementplus实现动态表单组件,通过拖拽组件到面板即可实现一个表单 支持各个组件的动态隐藏显示,动态表格弹窗式维护

    动态表单,VUE动态表单。基于vue+elementplus实现动态表单组件,通过拖拽组件到面板即可实现一个表单。支持各个组件的动态隐藏显示,动态表格弹窗式维护。

    【毕业设计】java-springboot-vue家居日用小百货交易网站实现源码(完整前后端+mysql+说明文档+LunW).zip

    【毕业设计】java-springboot-vue家居日用小百货交易网站实现源码(完整前后端+mysql+说明文档+LunW).zip

    【毕业设计】java-springboot+vue火锅店管理系统源码(完整前后端+mysql+说明文档+LunW).zip

    【毕业设计】java-springboot+vue火锅店管理系统源码(完整前后端+mysql+说明文档+LunW).zip

    基于springboot+vue的在线教育系统设计与实现(LW+PPT).zip

    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了微服务在线教育系统的开发全过程。通过分析微服务在线教育系统管理的不足,创建了一个计算机管理微服务在线教育系统的方案。文章介绍了微服务在线教育系统的系统分析部分,包括可行性分析等,系统设计部分主要介绍了系统功能设计和数据库设计。 本微服务在线教育系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,课程信息管理,课程类型管理,学科管理,购买的课程管理,职业规划管理,视频点播管理,我的笔记管理,我的课程管理,消息通知管理,学习交流,试卷管理,留言板管理,试题管理,系统管理,考试管理。用户功能有个人中心,用户管理,购买的课程管理,我的笔记管理,我的课程管理,消息通知管理。因而具有一定的实用性。 本站是一个B/S模式系统,采用SSM框架,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得微服务在线教育系统管理工作系统化、规范化。本系统的使用使管理人员从繁重的工作中解脱出来,实现无纸化办公,能够有效的提高微服务在线教育系统管理效率。 关键词:微服务在线教育系统;SSM框架;MYSQL数据库;Spring Boot

    基于Javascript实现,强化学习QLearning的一个贪吃蛇实例

    javascript 基于Javascript实现,强化学习QLearning的一个贪吃蛇实例.

    编程挑战_Chef卡牌游戏_学习资源_技能提升_1741399339.zip

    python教程学习

    基于springboot+vueIT技术交流和分享平台的设计与实现(LW).zip

    我国科学技术的不断发展,计算机的应用日渐成熟,其强大的功能给人们留下深刻的印象,它已经应用到了人类社会的各个层次的领域,发挥着重要的不可替换的作用。信息管理作为计算机应用的一部分,使用计算机进行管理,具有非常明显的优点,利用网络的优势特开发了本基于Spring Boot的IT技术交流和分享平台。 本IT技术交流和分享平台是基于Spring Boot框架,采用Java技术,MYSQL数据库进行开发的。系统具有灵活的一体化设计方式,圆满完成了整个系统的界面设计。本系统实现了用户功能模块和管理员功能模块两大部分,通过该系统用户可以快速进行IT技术交流和分享,管理员可登录系统后台对系统进行全面管理,确保系统正常稳定的运行。系统功能齐全,符合用户IT技术交流和分享的需求。 本文主要首先介绍了课题背景、设计原则和研究内容,系统采用的相关技术及开发平台,接着对本基于Spring Boot的IT技术交流和分享平台进行系统需求分析和设计,包括系统的功能模块,数据库的设计,系统结构以及系统界面设计等,最后对进行系统测试,完成本篇论文。 关键词:IT技术交流, Spring Boot框架, Java技术,MYSQL数据库

    疲劳检测yawn图片数据集

    疲劳检测yawn图片数据集

    JDK7通过java-jwt验证

    JDK7通过java-jwt验证

    【毕业设计】java-springboot+vue会议管理系统实现源码(完整前后端+mysql+说明文档+LunW).zip

    【毕业设计】java-springboot+vue会议管理系统实现源码(完整前后端+mysql+说明文档+LunW).zip

    sksalahuddin2828_Python_1741398999.zip

    python学习资源

    51CTO 1、技术解析篇-DeepSeek入门宝典 2、开发实战篇-DeepSeek入门宝典 3、行业应用篇-DeepSeek入门宝典 4、个人使用篇-DeepSeek入门宝典

    51CTO 1、技术解析篇-DeepSeek入门宝典 2、开发实战篇-DeepSeek入门宝典 3、行业应用篇-DeepSeek入门宝典 4、个人使用篇-DeepSeek入门宝典

    机器学习与模式识别领域的PRML习题解析手册

    内容概要:本文档是由高正奇编辑的针对模式识别和机器学习(PRML)教科书的一份详细的解答手册。文档覆盖了从基本概念如误差函数求导、贝叶斯定理应用到多元高斯分布计算、Gamma函数积分及其性质等一系列复杂问题的解决方案,以及涉及线性模型分类的基础练习题、条件概率和联合概率计算等入门级习题。每一题都经过细致推导,帮助学生加深对机器学习相关概念的理解并掌握具体的数学方法。 适合人群:主要适用于正在攻读机器学习、模式识别相关课程的学生,以及从事数据科学工作的专业人士作为深入理解和实践指南。 使用场景及目标:本手册旨在辅助教学过程中遇到的具体难题解析,在研究和实践中作为参考资料进行理论验证和技术难点突破,尤其有助于准备考试或者项目实施时需要巩固知识的应用场合。 其他说明:书中题目涵盖广泛,既有直观的概率论应用,也有复杂的积分变换技巧和最优化思路展示,对于希望提高自身计算能力和解决实际问题能力的学习者非常有价值。但要注意的是,部分内容较为深奥,可能不适合初学者自学使用,最好配合课堂讲解或其他教材一起学习效果更佳。

    编程语言_Python_入门之旅_教程_1741403040.zip

    python学习资源

    RFID-MATLAB的高等数学-CH06.rar

    RFID-MATLAB的高等数学-CH06.rar

    spaceX Ship Flight Test 8

    spaceX 动力学分析

    基于springboot+vue的美容院管理系统(LW).zip

    如今的信息时代,对信息的共享性,信息的流通性有着较高要求,因此传统管理方式就不适合。为了让美容院信息的管理模式进行升级,也为了更好的维护美容院信息,美容院管理系统的开发运用就显得很有必要。并且通过开发美容院管理系统,不仅可以让所学的SpringBoot框架得到实际运用,也可以掌握MySQL的使用方法,对自身编程能力也有一个检验和提升的过程。尤其是通过实践,可以对系统的开发流程加深印象,无论是前期的分析与设计,还是后期的编码测试等环节,都可以有一个深刻的了解。 美容院管理系统根据调研,确定其实现的功能主要包括美容用品管理,美容项目管理,美容部位管理,销量信息管理,订单管理,美容项目预约信息管理等功能。 借助于美容院管理系统这样的工具,让信息系统化,流程化,规范化是最终的发展结果,让其遵循实际操作流程的情况下,对美容院信息实施规范化处理,让美容院信息通过电子的方式进行保存,无论是管理人员检索美容院信息,维护美容院信息都可以便利化操作,真正缩短信息处理时间,节省人力和信息管理的成本。 关键字:美容院管理系统,SpringBoot框架,MySQL

Global site tag (gtag.js) - Google Analytics