目录
- 背景
- ThreadLocal的作用
- ThreadLocal特性
- ThreadLocal实现
- 1. T get()
- 2. set(T value)
- 3. remove()
- 验证
- 一个对象只存一个数据
- 多少个对象就能存多少个数据
背景
这两天稍微有点空,在追溯之前的android 7.0之前的手机用View.post 出现不执行的问题时,结识了ThreadLocal,且问题的原因也是系统内部使用ThreadLocal造成的。因此记录并分享之。
关于view.post不执行的坑点
ThreadLocal的作用
ThreadLocal的作用主要是做数据隔离,填充的数据(对象)只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,一个线程防止自己的变量被其它线程篡改。1
A线程存了一个对象objectA,此时B线程通过同一个ThreadLocal对象去取的话是取不到objectA的。
通俗的讲:
A、B钱包都没钱了,A从银行取了1000 人民币,装入了自己的钱包,B去商店买1000的商品,此时B从自己钱包里面拿钱时,钱包是空的。
AB好比是线程,存的数据就是1000人民币,消费的时候只能从自己钱包拿出来,只是A和B存到钱包的过程可以通过ThreadLocal来完成的。
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是2
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
ThreadLocal实现
ThreadLocal 对外一共提供了get、set和remove的函数,这里请注意set和get不是一般bean的get和set。而且巧妙的使用了调用栈关系,并取了当前调用的线程来做一些列的处理,所以与线程密切相关。调用线程指的是执行get、set、和remove的线程。
1. T get()
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);}
2. set(T value)
/*** 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);}
3. remove()
/*** Removes the entry for this variable in the current thread. If this call* is followed by a {@link #get()} before a {@link #set},* {@code #get()} will call {@link #initialValue()} and create a new* entry with the resulting value.** @since 1.5*/
public void remove() {Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values != null) {values.remove(this);}}
Values values(Thread current) {return current.localValues;
}
从get/set/remove实现中能看出,都是取当前线程,再从当前线程中拿出values,从values取出或put。而这个values是属于Thread的莫不是ThreadLocal的,这点要注意一下。value内部包含了数组,那么程序存取的数据对象就是放到这个数组中的,并且是以hash进行映射,和HashMap的实现不一样,思想几乎一样。
验证
接下来需要代码实际演示一下,不然死不了心。那么问题来了,从get和set来看都只有一个value参数,假如要set多个怎么办,而且set又是以ThreadLocal对象作为key去“存储”的。不要慌,既然是以ThreadLocal对象作为key,那就多创建几个ThreadLocal对象,多个对象被一个线程调用,那么多个数据就被存到了同一个线程的存储区(table)中。
一个对象只存一个数据
static void testThreadLoacal() {final ThreadLocal<String> threadLocal = new ThreadLocal<>();String str = "Test ThreadLocal";threadLocal.set(str);System.out.println("main thread set value:" + str);new Thread() {@Overridepublic void run() {String str = threadLocal.get();System.out.println("sub thread get value:" + str);}}.start();str = threadLocal.get();System.out.println("main thread get value:" + str);}
输出结果:
main thread set value:Test ThreadLocal
main thread get value:Test ThreadLocal
sub thread get value:null
一个线程set的值,其他线程取不到,只有相同线程才能取到。多个线程同时用一个ThreadLocal对象,其数据是还是属于各自线程。
多少个对象就能存多少个数据
由于是泛型,所以多个ThreadLocal不但可以存多个同类型的对象,甚至可以存多个不同类型的 对象。
static void testThreadLoacals() {final ThreadLocal<String> threadLocal = new ThreadLocal<>();final ThreadLocal<A> threadLocal1 = new ThreadLocal<>();String str = "main Thread";threadLocal.set(str);System.out.println("threadLocal main thread set str value:" + str);A a = new A();threadLocal1.set(a);System.out.println("threadLocal1 main thread set A class value:" + a);new Thread() {@Overridepublic void run() {String str = threadLocal.get();System.out.println("sub thread get value:" + str);str = "sub thread";threadLocal.set(str);System.out.println("sub thread set value:" + str);System.out.println("sub thread set value:" + threadLocal.get());}}.start();str = threadLocal.get();System.out.println("main thread 1 get value:" + str);System.out.println("main thread 1 get A class value:" + threadLocal1.get());try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}System.out.println("main thread 2 get value:" + threadLocal.get());}
threadLocal set 一个String
threadLocal1 set的是A类的对象
输出结果:
threadLocal main thread set str value:main Thread
threadLocal1 main thread set A class value:com.eagle.app.MainJava$A@12a3a380
main thread 1 get value:main Thread
main thread 1 get A class value:com.eagle.app.MainJava$A@12a3a380
sub thread get value:null
sub thread set value:sub thread
sub thread set value:sub thread
main thread 2 get value:main Thread
多个线程同时用多个ThreadLocal对象,其数据是还是属于各自线程。
Java中ThreadLocal的实际用途是啥 ↩︎
ingxin ThreadLocal ↩︎