上一篇:
一、背景
多个线程并发访问同一个共享数据的时候,并发修改同一个数据的时候,可能会导致数据错乱,必须要加一些并发同步机制;
如果每个 线程 拷贝一个线程自己本地的变量副本,每个线程就直接操作自己的本地副本就ok了,然后就跟其他的线程就没有冲突了,避免多个线程并发的访问同一个共享的数据;
二、demo
public static void main(String[] args) {
ThreadLocal <String> name = new thread Local<String>();
ThreadLocal<String> age = new ThreadLocal<String>();
new Thread(){
@Override
public void run() {
name.set("张三");
age.set("18");
System.out.println("线程1取name值:"+name.get());
System.out.println("线程1取age值:"+age.get());
}
}.start();
new Thread(){
@Override
public void run() {
name.set("李四");
age.set("22");
System.out.println("线程2取name值:"+name.get());
}
}.start();
}
三、源码分析
ThreadLocal在构造的时候就会根据next hashCode 方法算出一个 HashCode 出来;
因为static的原因,在每次new ThreadLocal时因为threadLocal hash Code的初始化,会使 Thread LocalHashCode值自增一次,增量为0x61c88647。
0x61c88647是 斐波那契 散列乘数,它的优点是通过它散列( Hash )出来的结果分布会比较均匀,可以很大程度上避免hash冲突,已初始容量16为例,hash并与15位运算计算数组下标结果如下:
hashCode |
数组下标 |
0x61c88647 |
7 |
0xc3910c8e |
14 |
0x255992d5 |
5 |
0x8722191c |
12 |
0xe8ea9f63 |
3 |
0x4ab325aa |
10 |
0xac7babf1 |
1 |
0xe443238 |
8 |
0x700cb87f |
15 |
set方法
public void set(T value) {
/**获取当前线程的ThreadLocalMap */
Thread t = Thread.currentThread();
/** 获取当前线程内部的ThreadLocalMap */
ThreadLocalMap map = getMap(t);
/** 如果不为空则进行赋值,如果为空则创建 */
if (map != null)
/** key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值 */
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal map getMap(Thread t) {
return t.threadLocals;
}
如果为空创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
/** INITIAL_CAPACITY:16
* 创建一个大小为16的数组
*/
table = new Entry[INITIAL_CAPACITY];
/** 根据key的Hash对数组下标取模之后放入对应数组下标中去
* 这里的threadLocalHashCode就是在ThreadLocal初始化时算出的Hash
*/
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
/** 设置扩容 阈值 */
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
/** 获取线程数组,根据key的Hash对数组长度-1取模算一个下标 */
Entry[] tab = table;
int len = tab.length;
/** 这里的threadLocalHashCode就是在ThreadLocal初始化时算出的Hash */
int i = key.threadLocalHashCode & (len-1);
/** 判断下标所在数据是否为空,如果不为空则更新值 */
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
/** 数组元素为空,直接创建Entry进行添加 */
tab[i] = new Entry(key, value);
int sz = ++size;
/** 满足条件数组扩容x2 */
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get方法
public T get() {
/** 获取当前线程map */
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
/** 如果map不为空*/
if (map != null) {
/** 将当前ThreadLocal作为key进行查询 */
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
/** 如果找不到元素则调用setInitialValue返回默认值,默认值:null */
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
/** 根据之前算出的Hash获取下标元素 */
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
setInitialValue方法
private T setInitialValue() {
/** 获取值null */
T value = initialValue();
/** 获取当前线程的map,设置默认值null入map */
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
/** 返回默认值 */
return value;
}
protected T initialValue() {
return null;
}
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
/** 如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量 */
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
/** 如果key相等则调用clear清空 */
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
注意事项
每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个 HashMap ),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。