1.what(ThreadLocal是什么)
在Android开发中相信大家经常用到Handler来将任务任务切换到handler所在的线程去执行,比如更新UI。所以很多人认为Handler的作用是更新UI,这说的的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景,具体来说是这样的:有时候需要在子线程中进行耗时的IO操作,这可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在UI上做一些改变,由于Android开发规范的限制,我们并不能在子线程中访问UI控件,否则就会触发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上来说,Handler并不是专门用于更新UI的,它只是常被大家用来更新UI。
Handler的运行需要底层的MessageQueue(消息队列)和Looper(循环器)的支撑,MessageQueue顾名思义就是一个存放消息的东西,虽然叫做“队列”,但是他的内部存储并不是队列形式的数据结构来存储数据,而是以单链表的数据结构来存储。但是MessageQueue的作用仅仅是来存储消息,并不会去处理消息,所以Looper(循环器)正好弥补了这个功能的缺失,Looper(循环器)以无限循环的方式去到MessageQueue(消息队列)里面去查看有没有新的消息,没有的话一直等待,有的话就会做出处理。但是!!!重点来了!!!!!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
线程是没有默认的looper,如果需要使用Handler就必须为线程创建Looper,那我们怎么拿到Looper呢?就是通过今天的主角ThreadLocal!
ThreadLocal:是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。而且又不会相互影响。
2.why(为什么要使用ThreadLocal )
举个列子:多个线程同时操作一个可以被创建和关闭的共享变量a(staticd修饰),场景一:没有同步,可能造成多次重复创建共享变量a。场景二:已经同步,可能当某个线程在操作变量a的时候,另外一个线程就只有等待,所以效率低。所以这个时候我们就可能想到其实我们也许不用将变量a进行共享(static进行修饰),既然多个线程需要对变量a进行操作,那么我们只需要在每个线程里面创建一个变量a,线程与线程之间又不存在依赖关系,就是说当某个线程操作变量a的时候并不需要担心其他线程是否对变量a做出改变。这个时候强大的ThreadLocal的应用场景就跃然纸上拉!
当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
3.HOW(ThreadLocal 方法怎么使用详解)
ThreadLocal 的几个方法: ThreadLocal 可以存储任何类型的变量对象, get返回的是一个Object对象,但是我们可以通过泛型来制定存储对象的类型。
public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { } //set()用来设置当前线程中变量的副本
public void remove() { } //remove()用来移除当前线程中变量的副本
protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的
Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表, 在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的, ThreadLocalMap是ThreadLocal类的一个内部类,这个Map里面的最小的存储单位是一个Entry, 它使用ThreadLocal作为key, 变量作为 value,这是因为在每一个线程里面,可能存在着多个ThreadLocal变量
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找
ThreadLocal是如何为每一个线程创建一个变量副本的,下面举一个例子来看一看。例子来源于 博客http://www.cnblogs.com/dolphin0520/p/3920407.html
这段代码的输出结果为:
从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会返回null;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
最后,这个人写得比我好,看不懂我的可以去看它的: 带你了解源码中的 ThreadLocal - 简书