android线程管理三(ThreadLocal)

来源:互联网 发布:亲淘软件下载 编辑:程序博客网 时间:2024/06/11 20:54

前言

   本篇主要讨论一下ThreadLocal,转载请注明出处:小石头的博客 http://blog.csdn.net/lu1024188315/article/details/74518599

一 什么是ThreadLocal

      ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突
      从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。 
      通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
      ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
      概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

二 ThreadLocal内部成员

    接下来,来看下ThreadLocal内部的几个成员。

ThreadLocalMap

     在《Thread》这篇文章中都已经提到过,在对Thread初始化的时候,会为Thread创建一个ThreadLocalMap,如下:
Thread#int2
private void init2(Thread parent) {        //获取线程parent的类加载器 ClassLoader类型        this.contextClassLoader = parent.getContextClassLoader();        this.inheritedAccessControlContext = AccessController.getContext();        if (parent.inheritableThreadLocals != null) {            //给当前线程创建ThreadLocalMap对象,并且继承parent的ThreadLocalMap中的数据            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(                    parent.inheritableThreadLocals);        
}     
那么ThreadLocalMap是什么?其部分源码如下:
ThreadLocal#ThreadLocalMap:
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;            }   }  .......}
说明,ThreadLocalMap是ThreadLocal的内部类继承了弱引用,从上述代码可以出其结构是key-value结构,key存储的是ThreadLocal类型,value是Object类型。完成可以把它看作是一个Map,用来保存变量的副本。

2 get函数分析

ThreadLocal#get:
public T get() {        //调用native层函数获取当前线程        Thread t = Thread.currentThread();        //获取当前线程的ThreadLocalMap对象        ThreadLocalMap map = getMap(t);        if (map != null) {           //获取此线程局部变量的当前线程副本中(线程本地变量)的值,如果这是线程第一次调用该方法,          //则调用setInitialValue函数创建并初始化此副本            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();}

3 set函数分析

ThreadLocal#set:
public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        //设置局部变量在当前线程中的副本的值,如果map为null,则创建它,并初始化副本        if (map != null)            map.set(this, value);        else            createMap(t, value);}

4 initialValue函数分析

ThreadLocal#initialValue:
protected T initialValue() {        return null;}
说明,这个函数顾名思义就是用来初始化线程局部变量,尽管这里没有体现,但它其实在setInitialValue中体现到了,ThreadLocal#setInitialValue 如下:
private T setInitialValue() {        //获取刚刚初始化的值并保存到map中        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;}

5 remove函数分析

ThreadLocal#remove
public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);}
说明,这个函数移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

三 ThreadLocal功能体现

     说了这么多,也许你对ThreadLocal的功能还是比较模糊,那么就来一个简单的实例来具体体现一下,这里只是部分代码,完整代码点击查看。
首先声明两个变量一个使用ThreadLocal,另一个不用ThreadLocal,并给它们初始化:
 /**普通数据*/    private int num = 0;    /** 经过ThreadLocal处理的数据*/    private ThreadLocal<Integer> threadLocalNum = new ThreadLocal<Integer>(){        @Override        protected Integer initialValue() {            //初始值为0            return 0;        }    };
再者建立两个子线程,并改变它们的值进行打印:
new Thread(new Runnable() {            @Override            public void run() {                num += 1;                threadLocalNum.set(threadLocalNum.get() + 1);                Log.e("线程1===num=====》","" + num);// 线程1===num=====》: 1                Log.e("线程1====threadLocal==》","" + threadLocalNum.get());// 线程1====threadLocal==》: 1            }        }).start();new Thread(new Runnable() {            @Override            public void run() {                ThreadlocalTestActivity.this.runOnUiThread(new Runnable() {                    @Override                    public void run() {                        num += 1;                        threadLocalNum.set(threadLocalNum.get() + 1);                        Log.e("线程2========》","" + num);// 线程2========》: 2                        Log.e("线程2====threadLocal==》","" + threadLocalNum.get());                       //线程2====threadLocal==》: 1                    }                });            }        }).start();      
说明,打印结果上面已经给出,从结果可以看出num在两个子线程中是受影响的,它的值先由0变为1再变为2,而threadLocalNum在两个子线程中,都是由0变为1,彼此之间不受影响,这个因为在两个子线程中都有一个threadLocalNum副本,当线程改变这个副本,另一个线程的副本不受影响没有做出改变。好,到这里应该对ThreadLocal有一个基本的认识了吧,那么我们继续探索。

四 ThreadLocal的实现版

     其实ThreadLocal其本质就是通过Map结构的集合,把变量的副本保存到本地,然后提供get、set、remove等方法来改变这个副本,而其他线程不受影响。
public class SimpleThreadLocal{        private Map valueMap = Collections.synchronizedMap(new HashMap());        /**         * 设置副本的值         * */        public void set(Object newValue) {            //①键为线程对象,值为本线程的变量副本            valueMap.put(Thread.currentThread(), newValue);        }        /**         *  获取副本的值         * */        public Object get() {            Thread currentThread = Thread.currentThread();            //②返回本线程对应的变量            Object o = valueMap.get(currentThread);            //③如果在Map中不存在,放到Map中保存起来。            if (o == null && !valueMap.containsKey(currentThread)) {                o = initialValue();                valueMap.put(currentThread, o);            }            return o;        }        /**         * 移除副本的值         * */        public void remove() {            valueMap.remove(Thread.currentThread());        }        /**         *         * */        public Object initialValue() {            return null;        }}

五 ThreadLocal与同步机制的比较

      ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

      在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大

      而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

     由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

      概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响

   

参考文献:
1《深入研究java.lang.ThreadLocal类
2《 彻底理解ThreadLocal
3《理解ThreadLocal







原创粉丝点击