ThreadLocal作为多线程中必备的一个神奇,保证数据安全,将线程的变量进行隔离,有非常多的使用场景.
例如:
假如我们商城下单的时候,我们需要用到多线程处理订单,就可以把提交的订单信息保存到ThreadLocal中,让每个线程处理自己的内容,线程之间没有各种麻烦的抢夺,数据资源不一致的问题。如下图,用户A,用户B 提交的数据互不干扰
很简单的demo,不做过多阐述
private ThreadLocal<Order> threadLocal = new ThreadLocal<>();
public void post(Order order) {
try {
threadLocal.set(order);
doOrder();
} finally {
threadLocal.remove();
}
}
private void doOrder() {
System.out.println("处理订单逻辑");
}
重点1,ThreadLocal 是怎么做到线程变量之间的隔离的? 上图
解释图形
-
首先通过
ThreadLocal<Order> threadLocal = new ThreadLocal<>();
初始化了一个ThreadLocal对象,就是上图中 ThreadLocal引用了 ThreadLocal对象 -
调用 threadLocal.set(order); 具体操作 :
//线程局部变量的当前线程副本设置为指定值。 大多数子类将不需要重写此方法,而仅依靠initialValue方法来设置线程initialValue的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); --> t.threadLocals
if (map != null)
map.set(this, value);
else
createMap(t, value); -->t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
Thread 类有个 ThreadLocalMap 成员变量,这个ThreadLocalMap中的Key是Threadlocal 对象,value是要存放的线程局部变量。 set 也就是向当前线程的ThreadLocalMap中存放了一个元素(Entry),Key是ThreadLocal对象,value就是order
- ThreadLocalMap 在Thread中
-
Thread 对象是Java语言中线程运行的载体,每个线程都有对应的Thread 对象,存放线程相关的一些信息
-
Thread类中有个成员变量ThreadlocalMap,普通的Map,key存放的是Threadlocal对象,value是你要跟线程绑定的值(线程隔离的变量),比如这里是用户信息对象(order).
为什么ThreadLcoalMap要定义在Thread中
ThreaLocalMap是自定义的哈希映射,仅适用于维护线程局部值。 没有操作导出到ThreadLocal类之外。 该类是包私有的,以允许声明Thread类中的字段。 为了帮助处理非常长的使用寿命,哈希表条目使用WeakReferences作为键。 但是,由于不使用参考队列,因此仅在表空间不足时,才保证删除过时的条目。
为什么不用Thread当作KEY ?取数据不是更加方便吗?
不可以,因为如果现在是只有一个Order对象操作,如果在家一个优惠券信息,那该怎么办?如果重新set不是就把原先的内容给覆盖了吗。简直是有点扯淡,
如果在ThreaLocalMap中重新添加一个第二个元素,
只需要 重新创建一个 private ThreadLocal<CardInfo> cardInfoThreadLocal = new ThreadLocal<>();
就可以了,这样当前线程就会有优惠券信息了
ThreadlocalMap 是什么数据结构实现的?
class ThreadLocalMap {
//初始容量
private static final int INITIAL_CAPACITY = 16;
//存放元素的数组
private Entry[] table;
//元素个数
private int size = 0;
}
table 就是存储线程局部变量的数组,数组元素是Entry类,Entry由key和value组成,key是Threadlocal对象,value是存放的对应线程变量
ThreadLocalMap发生Hash冲突怎么解决?
ThreadLocalMap 采用的是开放定址法,如果发生冲突,就往后找相邻的下一个节点,如果相邻的节点是空的,那么久直接存进去,如果不为空,继续往后查找,如果找到数据的最后也没有找到空的,就扩容
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// hashcode & 操作其实就是 %数组长度取余数,例如:数组长度是4,hashCode % (4-1) 就找到要存放元素的数组下标
int i = key.threadLocalHashCode & (len-1);
//找到数组的空槽(=null),一般ThreadlocalMap存放元素不会很多
for (Entry e = tab[i];
e != null; //找到数组的空槽(=null)
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key值一样,算是更新操作,直接替换
if (k == key) {
e.value = value;
return;
}
//key为空,做替换清理动作,这个后面聊WeakReference的时候讲
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//新new一个Entry
tab[i] = new Entry(key, value);
//数组元素个数+1
int sz = ++size;
//如果没清理掉元素或者存放元素个数超过数组阈值,进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//顺序遍历 +1 到了数组尾部,又回到数组头部(0这个位置)
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
// get()方法,根据ThreadLocal key获取线程变量
private Entry getEntry(ThreadLocal<?> key) {
//计算hash值 & 操作其实就是 %数组长度取余数,例如:数组长度是4,hashCode % (4-1) 就找到要查询的数组地址
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//快速判断 如果这个位置有值,key相等表示找到了,直接返回
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e); //miss之后顺序往后找(链地址法,这个后面再介绍)
}
如果不想让很多变量里面塞到ThreadLocalMap中造成扩容,怎么办 ?
答案就是 : 把ThreadLcoal的Value设置为Map值就行了