Android 线程本地变量<一> ThreadLocal源码解析
@(Android系统源码解析)[Android, ThreadLocal]
声明:转载请注明出处,知识有限,如有错误,请多多交流指正!
注:基于Android 6.0(API 23)源码
为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象
ThreadLocal定义和作用
1. 什么是ThreadLocal ?
JDK 1.6文档是这样定义的:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
2. ThreadLocal的作用
每个线程都有自己的局部变量,一个线程的本地变量对其他线程是不可见的,ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制
ThreadLocal代码使用
同时开辟四个线程,操作一个数据
用Map
存储的时候,线程并发访问,数据都不同
private static Map<Integer, Integer> map = new HashMap<>();
private static void test2() {
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int n = 0;
if (map.get(0) != null) {
n = map.get(0);
}
for (int j = 0; j < 5; j++) {
n++;
}
map.put(0, n);
System.out.println(Thread.currentThread().getName() + "-->" + map.get(0));
}
}, "Thread-Map-" + i).start();
}
}
运行结果
Thread-Map-1–>5
Thread-Map-2–>10
Thread-Map-0–>15
Thread-Map-3–>20
用ThreadLocal
存储数据,每个数据都是独立的,线程之间没有任何干扰
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
private static void test() {
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int n = 0;
if (threadLocal.get() != null) {
n = threadLocal.get();
}
for (int j = 0; j < 5; j++) {
n++;
}
threadLocal.set(n);
System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
}
}, "Thread-" + i).start();
}
}
运行结果
Thread-1–>5
Thread-0–>5
Thread-2–>5
Thread-3–>5
那么ThreadLocal
是如何关联线程的呢?可以看一看源码
ThreadLocal源码解析
注意:在Android中,ThreadLocal像是对原来的Java中的ThreadLcal做了优化的实现
1. ThreadLocal的结构
可以直观地看到在android中ThreadLocal
类提供了一些方法和一个静态内部类Values
,其中Values
主要是用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量
2. ThreadLocal的内部实现
成员变量
/** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference= new WeakReference<ThreadLocal<T>>(this);
通过弱引用存储ThreadLocal本身,主要是防止线程自身所带的数据都无法释放,避免OOM
private static AtomicInteger hashCounter = new AtomicInteger(0);
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
hashCounter
是线程安全的加减操作,getAndSet(int newValue)
取当前的值,并设置新的值;而0x61c88647 * 2作用是:
在Value
存在数据的主要存储数组table上,而table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value,`0x61c88647 * 2
保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位
方法
- public T get():返回此线程局部变量的当前线程副本中的值
public T get() {
// Optimized for the fast path.
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 获取当前线程的Value实例
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
// 如果键值的key的索引为index,则所对应到的value索引为index+1.
// 由此分析可知 hash&values.mask 获取的就是key的索引值
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
// 如果当前Value实例为空,则创建一个Value实例
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
可以看出get()
是通过value.table这个数组通过索引值来找到值得,
initializeValues(currentThread)
主要是直接new
出一个新的Values
对象
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
- protected T initialValue() 返回此线程局部变量的当前线程的“初始值”。
protected T initialValue() {
return null;
}
也就是默认值为Null
,当没有设置数据的时候,调用get()
的时候,就返回Null
;可以在创建ThreadLocal
的时候复写initialValue()
方法可以定义初始值
- public void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值.
public void set(T value) {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 获取当前线程的Value实例
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
// 将数据设置到Value中
values.put(this, value);
}
- public void remove() 移除此线程局部变量当前线程的值。
- Values values(Thread current) 通过线程获取Values对象。
Values内部存储数据,请看Android 线程本地变量<二> ThreadLocal Values源码解析
博客