Java ThreadLocal 深入剖析

最近看Android FrameWork层代码,看到了ThreadLocal这个类,有点儿陌生,就翻了各种相关博客一一拜读;自己随后又研究了一遍源码,发现自己的理解较之前阅读的博文有不同之处,所以决定自己写篇文章说说自己的理解,希望可以起到以下作用:

  • 可以疏通研究结果,加深自己的理解
  • 可以起到抛砖引玉的作用,帮助感兴趣的同学疏通思路
  • 分享学习经历,和大家一起交流和学习

一、 ThreadLocal 是什么

ThreadLocal 是Java类库的基础类,在包java.lang下面;

官方的解释是这样的:

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.

大致意思是:

可以实现线程的本地存储机制,ThreadLocal变量是一个不同线程可以拥有不同值的变量。所有的线程可以共享同一个ThreadLocal对象,但是不同线程访问的时候可以取得不同的值,而且任意一个线程对它的改变不会影响其他线程。类实现是支持null值的(可以在set和get方法传递和访问null值)。

概括来讲有三个特性:

- 不同线程访问时取得不同的值

- 任意线程对它的改变不影响其他线程

- 支持null

下面分别对这些特性进行实例验证,首先定义一个Test类,在此类中我们鉴证上边所提到的三个特性。类定义如下:

Test.java

public class Test{
    //定义ThreadLocal
    private static ThreadLocal<String> name;

    public static void main(String[] args) throws Exception{
        name = new ThreadLocal<String>();

        //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的作用

大家都知道它的使用场景大都是多线程编程,至于具体的作用,这个怎么说那?我觉得这个只能用一个泛的说法来定义,因为一个东西的功能属性定义了以后会限制大家的思路,就好比说菜刀是用来切菜的,好多人就不会用它切西瓜了。

这里,说下我对它的作用的认识,仅供参考,希望能有所帮助。这样来描述吧,当一个多线程的程序需要对多数线程的部分任务(就是run方法里的部分代码)进行封装时,在封装体里就可以用ThreadLocal来包装与线程相关的成员变量,从而保证线程访问的独占性,而且所有线程可以共享一个封装体对象;可以参考下Android里的Looper。不会用代码描述问题的程序员不是好程序员;

看代码:统计线程某段代码耗时的工具(为说明问题自造)

StatisticCostTime.java

// Class that statistic the cost time
public class StatisticCostTime{

    // record the startTime
    // private ThreadLocal<Long> startTime = new ThreadLocal<Long>();

    private long startTime;

    // private ThreadLocal<Long> costTime = new ThreadLocal<Long>();
    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!首先先弄明白为什么会有线程安全问题,无非两种情:

1、不该共享的资源,你在线程间共享了;

2、线程间共享的资源,你没有保证有序访问;

前者可以用“空间换时间”的方式解决,用ThreadLocal(也可以直接声明线程局部变量),后者用“时间换空间”的方式解决,显然这个就不是ThreadLocal力所能及的了。

三、 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最大的作用就是把线程共享变量转换成线程本地变量,实现线程之间的隔离。

时间: 2024-10-24 04:48:20

Java ThreadLocal 深入剖析的相关文章

关于Java ThreadLocal

转自:http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage/ What is ThreadLocal? A simple example As its name suggests, a single instance of ThreadLocal can store different values for each thread independently. Therefore, the value stor

JAVA垃圾收集机制剖析

1.垃圾收集算法的核心思想 Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象.该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用. 垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配.垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,

Java反射机制剖析(三)-简单谈谈动态代理

通过Java反射机制剖析(一)和Java反射机制剖析(二)的学习,已经对反射有了一定的了解,这一篇通过动态代理的例子来进一步学习反射机制. 1.     代理模式 代理模式就是为其他对象提供一种代理来控制对这个对象的访问.其实代理模式是在访问的对象时引入一定程度的间接性,这种间接性可以附加多种用途. 它 的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会 存在关联关系,一个代理类的对象与一个委托类的对象

Java反射机制剖析(四)-深度剖析动态代理原理及总结

动态代理类原理(示例代码参见java反射机制剖析(三)) a)  理解上面的动态代理示例流程 a)  理解上面的动态代理示例流程 b)  代理接口实现类源代码剖析 咱们一起来剖析一下代理实现类($Proxy0)的源代码和整个动态代理的流程. $Proxy0生成的代码如下: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; impo

深入研究JAVA ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量 (ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程

java ThreadLocal解读

Thread.java源码中: ThreadLocal.ThreadLocalMap threadLocals = null; 即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用. static class ThreadLocalMap { 所以每个线程都会有一个ThreadLocal.ThreadLocalMap对

Java ThreadLocal 简介

ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean.事务管理.任务调度.AOP等模块都出现了它们的身影,起着举足轻重的作用.要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒. 我们知道spring通过各种模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源.但这

java ThreadLocal(应用场景及使用方式及原理)

尽管ThreadLocal与并发问题相关,可是很多程序猿只将它作为一种用于"方便传參"的工具,胖哥觉得这或许并非ThreadLocal设计的目的,它本身是为线程安全和某些特定场景的问题而设计的. ThreadLocal是什么呢. 每一个ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全. 比如: public final static ThreadLocal<String> RESOURCE = n

理解Java ThreadLocal

ThreadLocal是什么 早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal很容易让人望文生义,想当然地认为是一个"本地线程".其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 当使用ThreadLocal维护变量