快速了解Java中ThreadLocal类
最近看Android FrameWork层代码,看到了ThreadLocal这个类,有点儿陌生,就翻了各种相关博客一一拜读;自己随后又研究了一遍源码,发现自己的理解较之前阅读的博文有不同之处,所以决定自己写篇文章说说自己的理解,希望可以起到以下作用: 一、 ThreadLocal 是什么 ThreadLocal 是Java类库的基础类,在包java.lang下面; 官方的解释是这样的: 大致意思是: 概括来讲有三个特性: - 不同线程访问时取得不同的值 下面分别对这些特性进行实例验证,首先定义一个Test类,在此类中我们鉴证上边所提到的三个特性。类定义如下: Test.java public class Test{ //定义ThreadLocal private static ThreadLocal name; public static void main(String[] args) throws Exception{ name = new ThreadLocal(); //Define Thread A Thread a = new Thread(){ public void run(){ System.out.println("Before invoke set,value is:"+name.get()); name.set(“Thread A”); System.out.println("After invoke set,value is:"+name.get()); } } ; //Define Thread B Thread b = new Thread(){ public void run(){ System.out.println("Before invoke set,value is :"+name.get()); name.set(“Thread B”); System.out.println("After invoke set,value is :"+name.get()); } } ; // Not invoke set,print the value is null System.out.println(name.get()); // Invoke set to fill a value name.set(“Thread Main”); // Start thread A a.start(); a.join(); // Print the value after changed the value by thread A System.out.println(name.get()); // Start thread B b.start(); b.join(); // Print the value after changed the value by thread B System.out.println(name.get()) } } 代码分析: 从定义中我们可以看到只声明了一个ThreadLocal对象,其他三个线程(主线程、Thread A和Thread B)共享同一个对象;然后,在不同的线程中修改对象的值和在不同的线程中访问对象的值,并在控制台输出查看结果。 看结果: 从控制台输出结果可以看到里边有三个null的输出,这个是因为在输出前没有对对象进行赋值,验证了支持null的特点;再者,还可以发现在每个线程我都对对象的值做了修改,但是在其他线程访问对象时并不是修改后的值,而是访问线程本地的值;这样也验证了其他两个特点。 二、 ThreadLocal的作用 大家都知道它的使用场景大都是多线程编程,至于具体的作用,这个怎么说那?我觉得这个只能用一个泛的说法来定义,因为一个东西的功能属性定义了以后会限制大家的思路,就好比说菜刀是用来切菜的,好多人就不会用它切西瓜了。 看代码:统计线程某段代码耗时的工具(为说明问题自造) StatisticCostTime.java // Class that statistic the cost time public class StatisticCostTime{ // record the startTime // private ThreadLocal startTime = new ThreadLocal(); private long startTime; // private ThreadLocal costTime = new ThreadLocal(); private long costTime; private StatisticCostTime(){ } //Singleton public static final StatisticCostTime shareInstance(){ return InstanceFactory.instance; } private static class InstanceFactory{ private static final StatisticCostTime instance = new StatisticCostTime(); } // start public void start(){ // startTime.set(System. nanoTime ()); startTime = System.nanoTime(); } // end public void end(){ // costTime.set(System. nanoTime () - startTime.get()); costTime = System.nanoTime() - startTime; } public long getStartTime(){ return startTime; // return startTime.get(); } public long getCostTime(){ // return costTime.get(); return costTime; } 好了,工具设计完工了,现在我们用它来统计一下线程耗时试试呗: Main.java public class Main{ public static void main(String[] args) throws Exception{ // Define the thread a Thread a = new Thread(){ public void run(){ try{ // start record time StatisticCostTime.shareInstance().start(); sleep(200); // print the start time of A System.out.println("A-startTime:"+StatisticCostTime.shareInstance().getStartTime()); // end the record StatisticCostTime.shareInstance().end(); // print the costTime of A System.out.println("A:"+StatisticCostTime.shareInstance().getCostTime()); } catch(Exception e){ } } } ; // start a a.start(); // Define thread b Thread b = new Thread(){ public void run(){ try{ // record the start time of B1 StatisticCostTime.shareInstance().start(); sleep(100); // print the start time to console System.out.println("B1-startTime:"+StatisticCostTime.shareInstance().getStartTime()); // end record start time of B1 StatisticCostTime.shareInstance().end(); // print the cost time of B1 System.out.println("B1:"+StatisticCostTime.shareInstance().getCostTime()); // start record time of B2 StatisticCostTime.shareInstance().start(); sleep(100); // print start time of B2 System.out.println("B2-startTime:"+StatisticCostTime.shareInstance().getStartTime()); // end record time of B2 StatisticCostTime.shareInstance().end(); // print cost time of B2 System.out.println("B2:"+StatisticCostTime.shareInstance().getCostTime()); } catch(Exception e){ } } } ; b.start(); } } 运行代码后输出结果是这样的 看结果是不是和我们预想的不一样,发现A的结果应该约等于B1+B2才对呀,怎么变成和B2一样了那?答案就是我们在定义startTime和costTime变量时,本意是不应共享的,应是线程独占的才对。而这里变量随单例共享了,所以当计算A的值时,其实startTime已经被B2修改了,所以就输出了和B2一样的结果。 现在我们把StatisticCostTime中注释掉的部分打开,换成ThreadLocal的声明方式试下。 呀!这下达到预期效果了,这时候有同学会说这不是可以线程并发访问了吗,是不是只要我用了ThreadLocal就可以保证线程安全了?答案是no!首先先弄明白为什么会有线程安全问题,无非两种情况: 三、 ThreadLocal 原理 实现原理其实很简单,每次对ThreadLocal 对象的读写操作其实是对线程的Values对象的读写操作;这里澄清一下,没有什么变量副本的创建,因为就没有用变量分配的内存空间来存T对象的,而是用它所在线程的Values来存T对象的;我们在线程中每次调用ThreadLocal的set方法时,实际上是将object写入线程对应Values对象的过程;调用ThreadLocal的get方法时,实际上是从线程对应Values对象取object的过程。 看源码: ThreadLocal 的成员变量set /** * Sets the value of this variable for the current thread. If set to * {@code null},the value will be set to null and the underlying entry will * still be present. * * @param value the new value of the variable for the caller thread. */ public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this,value); } TreadLocal 的成员方法get /** * Returns the value of this variable for the current thread. If an entry * doesn't yet exist for this variable on this thread,this method will * create an entry,populating the value with the result of * {@link #initialValue()}. * * @return the current value of the variable for the calling thread. */ @SuppressWarnings("unchecked") public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); } ThreadLocal的成员方法initializeValues /** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values(); } ThreadLocal 的成员方法values /** * Gets Values instance for this thread and variable type. */ Values values(Thread current) { return current.localValues; } 那这个Values又是怎样读写Object那? Values是作为ThreadLocal的内部类存在的;这个Values里包括了一个重要数组Object[],这个数据就是解答问题的关键部分,它是用来存储线程本地各种类型TreadLocal变量用的;那么问题来了,具体取某个类型的变量时是怎么保证不取到其他类型的值那?按一般的做法会用一个Map根据key-value映射一下的;对的,思路就是这个思路,但是这里并没有用Map来实现,是用一个Object[]实现的Map机制;但是,若要用Map理解的话,也是不可以的,因为机制是相同的;key其实上对应ThreadLocal的弱引用,value就对应我们传进去的Object。 解释下是怎么用Object[]实现Map机制的(参考图1);它是用数组下标的奇偶来区分key和value的,就是下表是偶数的位置存储key,奇数存储value,就是这样搞得;感兴趣的同学如果想知道算法实现的话,可以深入研究一下,这里我不在详述了。 结合前面第一个实例分析下存储情况: 当程序执行时存在A,B和main三个线程,分别在线程中调用name.set()时同时针对三个线程实例在堆区分配了三块相同的内存空间来存储Values对象,以name引用作为key,具体的object作为值存进三个不同的Object[](参看下图): 四、 总结 ThreadLocal 不能完全解决多线程编程时的并发问题,这种问题还要根据不同的情况选择不同的解决方案,“空间换时间”还是“时间换空间”。 ThreadLocal最大的作用就是把线程共享变量转换成线程本地变量,实现线程之间的隔离。 以上就是本文关于快速了解Java中ThreadLocal的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。感谢朋友们对本站的支持。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |