一、ThreadLocal的使用
ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK
5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动
被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它
可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为
了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get()
或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一
个 null。
线程类
/**
* 构建线程类
*/
public class ThreadInheritTest extends Thread{
private ThreadLocal<String> threadLocal;
private StringBuilder stringBuilder;
public ThreadInheritTest(ThreadLocal<String> threadLocal, StringBuilder stringBuilder){
this.threadLocal = threadLocal;
// ***这里有个问题??会发现在子线程里没有值***
threadLocal.set("本地变量");
this.stringBuilder = stringBuilder;
}
@Override
public void run() {
// 线程本地变量
threadLocal.set(Thread.currentThread().getName());
// 线程共享变量
this.stringBuilder = this.stringBuilder.append(Thread.currentThread().getName());
// 只有当前线程的信息
System.out.println("ThreadLocal变量"+Thread.currentThread().getName()+":"+threadLocal.get());
System.out.println("----------------------------------------------------------------");
// 会把所有线程的结果都拼接上
System.out.println("普通变量"+Thread.currentThread().getName()+":"+this.stringBuilder);
}
}
在主线程中,使用ThreadLocal变量设置值时,会发现在子线程里没有值?
测试类
public class ThreadLocalMapTest {
public static ThreadLocal<String> local = new ThreadLocal<>();
public static StringBuilder builder = new StringBuilder("普通的变量");
public static void main(String[] args) {
for(int i=0;i<20;i++){
ThreadInheritTest thread = new ThreadInheritTest(local,builder);
thread .setName("第"+i+"个线程");
thread.start();
}
}
}
从运行结果可以看出,会发现同一个ThreadLocal变量不会线程共享
ThreadLocal变量第0个线程:第0个线程
----------------------------------------------------------------
普通变量第0个线程:普通的变量第0个线程
ThreadLocal变量第4个线程:第4个线程
----------------------------------------------------------------
普通变量第4个线程:普通的变量第0个线程第4个线程
ThreadLocal变量第5个线程:第5个线程
----------------------------------------------------------------
普通变量第5个线程:普通的变量第0个线程第4个线程第5个线程
ThreadLocal变量第2个线程:第2个线程
----------------------------------------------------------------
普通变量第2个线程:普通的变量第0个线程第4个线程第5个线程第2个线程
ThreadLocal变量第8个线程:第8个线程
----------------------------------------------------------------
普通变量第8个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程
ThreadLocal变量第1个线程:第1个线程
----------------------------------------------------------------
普通变量第1个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程
ThreadLocal变量第10个线程:第10个线程
----------------------------------------------------------------
普通变量第10个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程
ThreadLocal变量第6个线程:第6个线程
----------------------------------------------------------------
普通变量第6个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程
ThreadLocal变量第11个线程:第11个线程
----------------------------------------------------------------
普通变量第11个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程
ThreadLocal变量第17个线程:第17个线程
----------------------------------------------------------------
普通变量第17个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程
ThreadLocal变量第12个线程:第12个线程
----------------------------------------------------------------
普通变量第12个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程
ThreadLocal变量第13个线程:第13个线程
----------------------------------------------------------------
普通变量第13个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程
ThreadLocal变量第3个线程:第3个线程
----------------------------------------------------------------
普通变量第3个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程
ThreadLocal变量第15个线程:第15个线程
----------------------------------------------------------------
普通变量第15个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程
ThreadLocal变量第14个线程:第14个线程
----------------------------------------------------------------
ThreadLocal变量第19个线程:第19个线程
----------------------------------------------------------------
ThreadLocal变量第9个线程:第9个线程
----------------------------------------------------------------
普通变量第14个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程
ThreadLocal变量第7个线程:第7个线程
ThreadLocal变量第18个线程:第18个线程
----------------------------------------------------------------
普通变量第18个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程
普通变量第9个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程
普通变量第19个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程
ThreadLocal变量第16个线程:第16个线程
----------------------------------------------------------------
----------------------------------------------------------------
普通变量第16个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程
普通变量第7个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程
ThreadLocal原理
ThreadLocal的线程独享变量是通过ThreadLocalMap的存储结构类实现的
以共享变量ThreadLocal的实例对象为Key,设置的实例对象为Value的Map的机构。
ThreadLocal的set设置过程
ThreadLocalMap的类是定义在ThreadLocal里面的内部类,下面是源码:
但是使用的变量定义却是在在Thread中:
ThreadLocalMap的使用过程分析
- 初始化
ThreadLocalMap是数组存储结构
当我们在线程中,第一次给ThreadLocal变量负值时,就会触发threadLocals的初始化。
源码:
/**
* 设置
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 判断是不是需要初始化
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* 创建 ThreadLocalMap
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
初始化容量和HashMap默认一样是 16
注意:这里的扩容实在三分之二处
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
- 放置值
通过hash算法找到数组的指定位置
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// hash计算 确定数组位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
//为了解决hash冲突
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;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// 先插入,后判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在设置值的过程涉及到hash碰撞的解决方法——使用开放地址法(为线性探测再散列),扩容(单线程)。
ThreadLocal的问题
为什么要用ThreadLocal?
JDBC的链接告诉我们,ThreadLocal可以实现跨方法的参数传递
应用场景:日志流水TraceId链路追踪,JDBC事务链接
ThreadLoca会引发内存泄漏吗?怎么避免?
会,虽然ThreadLocalMap的Key是弱引用,当是Value是强引用。
手动调用remove方法就可以去掉这个强引用。
扩展:
hash算法碰撞的解决方案
开放定址法:
基本思想是,出现冲突后按照一定算法查找一个空位置存放,根据算法的不
同又可以分为线性探测再散列、二次探测再散列、伪随机探测再散列。
线性探测再散列即依次向后查找,二次探测再散列,即依次向前后查找,增
量为 1、2、3 的二次方,伪随机,顾名思义就是随机产生一个增量位移。
链地址法:
这种方法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的
单链表,并将单链表的头指针存在哈希表的第 i 个单元中,因而查找、插入和删
除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。Java
里的 HashMap 用的就是链地址法,为了避免 hash 洪水攻击,1.8 版本开始还引
入了红黑树。
再哈希法:
这种方法是同时构造多个不同的哈希函数:Hi=RH1(key) i=1,2,…,k
当哈希地址 Hi=RH1(key)发生冲突时,再计算 Hi=RH2(key)……,直到冲突
不再产生。这种方法不易产生聚集,但增加了计算时间。
建立公共溢出区
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本
表发生冲突的元素,一律填入溢出表。