ThreadLocal理解与使用

来源:互联网 发布:alpine python 编辑:程序博客网 时间:2024/06/10 05:40
    在看FrameWork源码时,在ActivityThread类中有一个ThreadLocal变量,是这么定义的:
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();

     再加之,项目中和其它插件源码这个ThreadLocal出现的频率很高,所以决定,对他好好研究一番。

     ThreadLocal可以理解为主要解决多线程并发的问题,实际上,在使用场合上也多是在处理多线程并发的时候会用到ThreadLocal这个类。然而ThreadL并不是一个Thread,而是代表了线程变量的副本。ThreadLocal只对外开放了四个方法,分别是构造器ThreadLocal()、set()、get()、和remove(),当然,还有一个initialValue()方法是protected类型,这个方法应该是提供给开发人员去重写的,至于作用,我们下面会一一讲到。

     我们从使用的角度来分析源码,首先是new一个ThreadLocal对象,它就是一个空的无参数构造器,没啥好说的,然后我们就调用set()方法进行设置副本变量:

    

    public void set(T value) {       //获取当前线程        Thread t = Thread.currentThread();        //获取当前线程的ThreadLocalMap,也就是Thread类的threadLocals变量,        ThreadLocalMap map = getMap(t);        //如果为不为空就赋值,如果为空就创建一个ThreadLocalMap        if (map != null)            map.set(this, value);        else            createMap(t, value);    }
       从上面的set()方法可以看出来,在调用set()方法将对象赋值给ThreadLocal时,会首先根据当前的线程来获取其threadLocals变量(也就是ThreadLocalMap对象),如果该对象不为空就直接以ThreadLoca为key值,T对象为value值保存起来,(注意,由于key值是ThreadLocal,所以一个线程可以有多个ThreadLocal对象)。如果该对象为空就创建一个ThreadLocalMap,我们先来看看getMap()方法:

     

    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }
      可以看到,没什么复杂的逻辑,直接返回Thread.threadLocals,我们看Thread源码可以发现,该变量就是一个ThradLocalMap对象。然后我们看下createMap():

   

    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }
     可以看到,是直接new一个ThreadLocalMap对象,并赋值给Thread.threadLocals。因此,set()方法就是这么简单的一个逻辑,在这里就总结一下:我们在不同的线程中调用set()方法时会为每一个线程的threadLocals赋值一个ThradLocalMap实例,该实例就包含了我们的副本变量,因此Thread的threadLocals变量才是真正意义上的副本,ThreadLocal不过是一个桥梁而已。

     然后赋值问题解决,下面看下怎么获取到变量呢,看下get()方法:

     

    public T get() {        //获取到当前线程        Thread t = Thread.currentThread();        //获取该线程的ThreadLocalMap实例        ThreadLocalMap map = getMap(t);        //如果不为空就调用ThreadLocalMap获取变量实体,否则就调用setInitialValue()返回一个初始值        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }
     getMap()方法在上面我们已经说过,再看下setInitialValue():

      

    private T setInitialValue() {        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;    }
    可以看到,setInitialValue()是调用initialValue()来生成一个初始值,然后返回的,看下initialValue()做了什么:

     

    protected T initialValue() {        return null;    }
    正如你所见,他直接返回了null,我们再创建ThreadLocal时最好重写一下这个方法,避免我们没有set()就直接get()报控制针错误。好了,到这里我们知道了,如果在线程中我们没有先调用set()方法而是直接get()方法且没有重写initialValue()方法就会返回一个null值,就会出现空指针错误。所以我们在创建ThreadLocal时最好重写一下initialValue()方法。

      OK,下面我们来用一个例子来实践一下:

    (1)不重写initialValue()方法,线程不set()直接get():

     

/** 创建一个ThradLocal实例 */private static ThreadLocal<StudentInfo> threadLocal = new ThreadLocal<StudentInfo>();public static void main(String[] args) {StudentInfo info = new StudentInfo("sdew23", "张三", "男");// 为主线程保存一个副本StudentInfo对象threadLocal.set(info);System.out.println(threadLocal.get());System.out.println(Thread.currentThread().getName());// 开启子线程new Thread(new Runnable() {@Overridepublic void run() {// threadLocal.set(info);System.out.println(threadLocal.get());System.out.println(Thread.currentThread().getName());}}).start();}
       看到,在主线程中先set()了然后在get()出来打印,而在子线程中没有set()(被我注释掉了),然后直接打印,执行结果如下:

       

        从结果可以看到,主线程是打印出信息来了的,但是子线程打印出null,因为在子线程既没有set()又没有重写initialValue()方法,下面我们来重写initialValue()方法试试,在创建ThreadLocal时改为如下:

/** 创建一个ThradLocal实例 */private static ThreadLocal<StudentInfo> threadLocal = new ThreadLocal<StudentInfo>() {@Overridepublic StudentInfo initialValue() {return new StudentInfo("sss", "小西", "女");}};    public static void main(String[] args) {        StudentInfo info = new StudentInfo("sdew23", "张三", "男");        // 为主线程保存一个副本StudentInfo对象        threadLocal.set(info);        System.out.println(threadLocal.get());        System.out.println(Thread.currentThread().getName());        // 开启子线程        new Thread(new Runnable() {            @Override            public void run() {                // threadLocal.set(info);                System.out.println(threadLocal.get());                System.out.println(Thread.currentThread().getName());            }        }).start();    } 
         仅仅是在创建ThreadLocal时重写了initialValue()方法,其他都没有改变,下面再看下执行结果:

   

      可以看到,尽管,子线程没有set()变量但是打印出了其初始值,这样可以避免空指针错误了。当然,如果子线程调用了set()的话,那么他们get()的结果就是一样的了,只不过此时他们的对象是两个一样的“副本”,难道这样就可以避免多线程并发访问资源的冲突了吗?答案是否定的。回过头去看一下ThreadLocal的实现原理:每一个线程在使用ThreadLocal的时候实际上是以ThreadLocal为key值共享的对象为value值保存在Thread.threadLocalMaps变量中也就是ThreadLocalMap实例,因此ThreadLocal仅仅是作为一个key值来保存,多线程在使用同一个ThreadLocal或者不同的ThreadLocal但是保存相同的共享对象时他们的threadLocalMaps值是相同的,因此如果有共享资源发生冲突问题,ThreadLocal并不能解决!如果要解决并发冲突的问题要么使用安全对象,要么使用上锁机制来保证多线程顺序访问!

      实际上ThreadLocal的使用场景是这样的:多个线程共享同一资源(属性)时不必在意该资源的状态,也就是说,某线程对该资源的修改不会影响其他线程的执行。而如果某线程对该资源的修改会影响到其他线程的执行则应该上锁来保证同步执行。

        为了证实多线程的共享对象是同一个对象这个观点,我们来做下面这个例子:

     

public static void main(String[] args) {StudentInfo info = new StudentInfo("sdew23", "张三", "男");// 为主线程保存一个副本StudentInfo对象threadLocal.set(info);// 开启子线程new Thread(new Runnable() {@Overridepublic void run() {threadLocal.set(info);StudentInfo infos = threadLocal.get();// 获取到子线程的变量数据然后修改一个属性infos.name = "哈哈";System.out.println(Thread.currentThread().getName() + "-->"+ infos);}}).start();// 打印主线程的变量System.out.println(Thread.currentThread().getName() + "-->"+ threadLocal.get());}
      如上所示,在主线程中设置了一个StudentInfo对象,在子线程中也设置了该对象,但是在设置后立刻取出并给该对象换一个名字,最后打印出来的结果(当然由于线程是抢占CPU运行的,主线程和子线程谁先抢到还不一定)有三种情况:

      (1)主线程先抢到CPU,并且主线程的StudentInfo叫”张三“,子线程叫”哈哈“,因为子线程还没来得及修改StudentInfo的名字。

       

      (2)主线程先抢到了CPU,还没来得及get()到StudentInfo就又被子线程抢过去修改了属性,此时子线程还没来得及打印就被主线程抢回去打印了,此时主线程虽然最先抢到CPU,但是期间被子线程修改了其StudentInfo名字,所以,此时的结果就是主线程叫”哈哈“,子线程也叫”哈哈“:

       

    (3)子线程先抢到CPU并且顺利执行完,此时两个都是”哈哈“:

     

      OK,上面例子也足以说明,ThreadLocal并不能够解决资源冲突问题的。正确理解ThreadLocal推荐下面四篇文章,是同一个作者写的:

    http://www.iteye.com/topic/777716

    http://www.iteye.com/topic/777716

   http://www.iteye.com/topic/757478

   http://www.iteye.com/topic/179040

0 0
原创粉丝点击