ThreadLocal简介:
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般会在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
ThreadLocal的简单使用:
package demo;
public class test2 {
//定义一个ThreadLocal变量
static ThreadLocal<Student> t = new ThreadLocal<Student>();
public static void main(String[] args) {
new Thread(() ->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一:"+t.get());
}).start();
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.set(new Student("李四","25"));
System.out.println("线程二:"+t.get());
t.set(new Student("张三","23"));
System.out.println("线程二:"+t.get());
}).start();
}
}
class Student{
private String name;
private String age;
public Student(String name, String age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
运行结果:
从运行结果来看,线程一在没有给t赋值的情况下拿到的是null,而线程二先赋值是可以拿到值的。
ThreadLocal实现原理:
ThreadLocal内部类:
ThreadLocalMap是ThreadLocal的的一个静态内部类,Entry是ThreadLocalMap的一个静态内部类。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 与此ThreadLocal关联的值. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这个类继承自弱引用类,ThreadLocal作为引用对象。它和普通的Map节点类不太相同,普通的Map节点类会含有两个属性Key,value。而这个entry节点类只有value属性,原因是因为它继承了弱引用类,会有一个对引用对象(T)
ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。
ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 与此ThreadLocal关联的值. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;//初始容量-必须是2的幂
/**
* 该表,根据需要调整大小
* table.length必须始终为2的幂。
*/
private Entry[] table;
/**
* map的实际大小
*/
private int size = 0;
/**
* 下一个要调整大小的大小值。
*/
private int threshold; // Default to 0
/**
* 设置阈值大小以保持最坏的2/3负载系数
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 计算下一个索引,其实就是加一并且轮询这个数组
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
*计算前一个索引,其实就是减一并且轮询这个数组
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**构造器:
* 构造一个最初包含(firstKey,firstValue)的新map。
* ThreadLocalMaps是延迟构造的,因此只有在至少要放置一个条目时才创建一个
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* 私有构造器
* 仅由createInheritedMap调用。
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
/**
* 获取与此key相对应的entry对象
*/
private Entry getEntry(ThreadLocal<?> key) {
//获取key对应的索引
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//判断该节点是否为null和该节点的引用是否等于该key
if (e != null && e.get() == key)
return e;//是 返回
else//否则,调用getEntryAfterMiss()方法
return getEntryAfterMiss(key, i, e);
}
/**
* 当在其直接哈希表中找不到key对应的entry对象时使用的getEntry方法的版本。.
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {//该节点为null直接返回
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)//如果该节点的引用为null,清楚该节点
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* 设置与键关联的值。
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;//拿到当前线程的threadLocals的entry数组
int len = tab.length;//获取数组长度
int i = key.threadLocalHashCode & (len-1);//计算key在entry数组中的位置
//循环遍历数组,如果此位置为null,跳出循环,否则顺序向下遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();//拿到当前位置的entry的threadLocal
if (k == key) {//表明在此之前插入过该key的值,直接覆盖以前的value即可
e.value = value;
return;
}
//因为entry的key存放的是ThreadLocal的弱引用,弱引用只要gc来过就会回收,当前entry!=null&&k=null 表明在此之前gc经过回收,导致当前位置entry的key被回收,在此需要特殊处理,防止内存泄漏
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//当前位置的entry为null直接new一个entry并赋值给当前位置 threadLocal为key,value为值
tab[i] = new Entry(key, value);
int sz = ++size;//长度自增
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();//判断清理掉空Key的table数组大小是否大于阈值,是否需要重哈希
}
/**
* 删除此key对应的entry
*/
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* 替换entry.get()为null的entry,即引用对象被gc了
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//以staleSlot位置往前遍历查看是否还有entry.get()为null的entry对象
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))//注意这里i的计算
//如果遍历整个数组没有发现null就会回到staleSlot位置跳出循环
if (e.get() == null)
slotToExpunge = i;
//以staleSlot位置往后遍历,直到entry为null跳出,否则顺序往后遍历
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;//覆盖之前的值
//交换当前位置和staleSlot位置的entry
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 去除e.get()==null的节点
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 如果跳出循环,就在staleSlot位置新建一个entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//清理entry.get()为null的entry对象
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
*通过重新散列位于staleSlot和下一个null插槽之间的任何可能冲突的条目来清除
* 陈旧的条目。 这还会清除尾随null之前遇到的所有其他过时的条目
*
* @param staleSlot 已知具有空键的插槽索引
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 删除该位置的entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 重新哈希,直到遇到null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// 我们必须扫描到null为止,因为可能有多个条目过时。
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/*启发式扫描某些单元以查找陈旧条目。 当添加了新元素或已删除另一旧元素时,将调用此方法。
它执行对数扫描,作为无扫描(快速但保留垃圾)和与元素数量成正比的扫描数量之间的平衡,
这会发现所有垃圾,但会导致某些插入花费O(n)时间。
参数:
i –一个不持有陈旧条目的职位。 扫描从i之后的元素开始。
n –扫描控制:除非找到过时的条目,否则将扫描log2(n)单元格,在这种情况下,
将扫描另外的log2(table.length)-1个单元格。 从插入处调用时,此参数是元素数,
而从replaceStaleEntry中调用时,它是表长度。 (注意:可以通过对n加权而不是仅使用直接对数n来
将所有这些更改为或多或少具有攻击性。但是此版本简单,快速,并且似乎运行良好。)
返回值:
如果已删除任何过时的条目,则为true。*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* 重哈希
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* 将表的容量加倍
*/
private void resize() {
Entry[] oldTab = table;//拿到扩容之前的entry数组
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];//创建一个新的entry数组
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
/**
* 清除表中所有key为null的条目
*/
private void expungeStaleEntries() {
Entry[] tab = table; //遍历整个数组清理掉entry不为null,key为null的entry
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
ThreadLocal
public class ThreadLocal<T> {
/*ThreadLocals调用set方法时拿的ThreadLocal对象的hash值*/
private final int threadLocalHashCode = nextHashCode();
/*下一个要给出的哈希码。从零开始*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/* HASH_INCREMENT = 0x61c88647是一个魔法数,可以减少hash冲突,通过nextHashCode.getAndAdd(HASH_INCREMENT)
方法会转化为二进制数据,主要作用是增加哈希值,减少哈希冲突*/
private static final int HASH_INCREMENT = 0x61c88647;
/*返回下一个哈希码 */
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 返回此线程局部变量的当前线程的“初始值”。 除非线程先前调用了set方法,
* 否则线程第一次使用get方法访问该变量时将调用此方法,在这种情况下,不
* 会为该线程调用initialValue方法。 通常,每个线程最多调用一次此方法,
* 但是在随后依次调用remove和get情况下,可以再次调用此方法。
* 这个实现只是返回null ; 如果希望线程局部变量具有非null的初始值,
* 则必须将ThreadLocal子类化,并重写此方法。
*/
protected T initialValue() {
return null;
}
/**
* 创建线程局部变量。 变量的初始值是通过在Supplier上调用get方法确定的
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
/**
* 创建一个线程局部变量
*/
public ThreadLocal() {
}
/**
* 返回此线程局部变量的当前线程副本中的值。
* 如果该变量没有当前线程的值,则首先将其初始化为通过调用initialValue方法返回的值。
*/
public T get() {
Thread t = Thread.currentThread();//拿到当前线程
ThreadLocalMap map = getMap(t);//拿到当前线程的threadLocals变量
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//拿到一个以threadLocal为key的entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//当前线程的threadLocals变量为空,调用setInitialValue()
}
/**
* set()的变体为了建立初始值。 如果用户已经重写了set()方法,请使用它代替set()
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
/**
* 设置线程局部变量的值
*/
public void set(T value) {
Thread t = Thread.currentThread();//拿到当前线程
ThreadLocalMap map = getMap(t);//拿到当前线程的threadLocals变量
if (map != null)//如果map不null,以本地变量为key,value为值插入map
map.set(this, value);
else//map为null,以当前线程调用createMap()方法创建一个新的map
createMap(t, value);
}
/**
* 删除当前线程的对于此线程局部变量的值
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 获取与t线程相关联的threadLocals
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 创建与ThreadLocal关联的map。
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 创建继承的线程局部变量的映射的工厂方法。 设计为仅从Thread构造函数调用
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 方法childValue在InheritableThreadLocal子类中可见地定义,但是在此内部定
* 义是为了提供createInheritedMap工厂方法而无需在InheritableThreadLocal中
* 子类化地图类。 此技术优于在方法中嵌入测试实例的替代方法
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
ThreadLocal与synchronized的区别:
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步机制采用‘以时间换空间’的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用‘以空间换时间’的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而不互相干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |