ThreadLoad
ThreadLocal作用
- ThreadLocal的作用,可以实现在同一个线程数据共享,从而解决多线程数据安全问题
- ThreadLocal可以给当前线程关联一个数据(普通变量、对象、数组)
set方法
- ThreadLocal可以像 Map一样存取数据,key为当前线程
get方法
- 每一个ThreadLocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal对象实例
- 每个ThreadLocal对象实例定义的时候,一般为static类型
public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
- ThreadLocal中保存数据,在线程销毁后,会自动释放
ThreadLoad 使用
API | 解释 |
---|---|
new ThreadLocal<>(); | 创建 ThreadLocal 对象 |
set(xxx) | 将 xxx 对象 放入 ThreadLocal 对象中 |
get() | 取出 当前线程 对应的 ThreadLocal 对象 存放的 值 |
ThreadLoad介绍
例子
tomcat每接收一个请求,都会开启一个线程,该线程完成的任务可能是一个事务控制操作,在一次请求中会调用多个service,多个DAO,我们希望在当中有个数据是共享的并且是线程安全的。当把所有事情做完后,最后commit
ThreadLoad就能很好的解决事务安全和事务一致性的问题
ThreadLoad 环境搭建
ThreadLoad:同一个线程中,共享数据并且是线程安全的
应用案例要实现的操作
在T1类中开启一个线程,在线程(thread0)中放入dog对象-->
并且调用service中的方法可以得到thread0中存放的dog对象,并且调用DAO中的方法-->
DAO可以得到thread0中的存放的dog对象
在整个过程中,都是同一个线程在执行操作。ThreadLoad中存放的数据,在同一个线程中都可以共享
T1
package com.study.threadlocal;
/**
* @author 珀筱
*/
@SuppressWarnings({"all"})
public class T1 {
//Task是线程类->内部类/线程
public static class Task implements Runnable {
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
System.out.println("在run方法中线程=" + Thread.currentThread().getName());
new T1Service().update();
}
}
public static void main(String[] args) {
new Thread(new Task()).start();///主线程启动一个新的线程,注意不是主线程
}
}
T1Service
package com.study.threadlocal;
/**
* @author 珀筱
*/
public class T1Service {
public void update() {
//获取当前线程
String name = Thread.currentThread().getName();
System.out.println("在T1Service的update()线程name=" + name);
//调用dao-update
new T2DAO().update();
}
}
T2DAO
package com.study.threadlocal;
/**
* @author 珀筱
*/
public class T2DAO {
public void update() {
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("在T2DAO的update()线程是= " + name);
}
}
ThreadLoad使用
T1
package com.study.threadlocal;
/**
* @author 珀筱
*/
@SuppressWarnings({"all"})
public class T1 {
//创建Threadlocal对象,并设置为public static
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
//Task是线程类->内部类/线程
public static class Task implements Runnable {
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
System.out.println( "Task放入了dog=" + dog);
threadLocal1.set(dog);
System.out.println("在run方法中线程=" + Thread.currentThread().getName());
new T1Service().update();
}
}
public static void main(String[] args) {
new Thread(new Task()).start();///主线程启动一个新的线程,注意不是主线程
}
}
T1Service
package com.study.threadlocal;
/**
* @author 珀筱
*/
public class T1Service {
public void update() {
//取出threadLocal1对象关联的对象
Object o = T1.threadLocal1.get();
//获取当前线程
String name = Thread.currentThread().getName();
System.out.println("在T1Service的update()线程name=" + name + ",取出dog=" + o);
//调用dao-update
new T2DAO().update();
}
}
T2DAO
package com.study.threadlocal;
/**
* @author 珀筱
*/
public class T2DAO {
public void update() {
//取出线程关联的threadLocal1对象的的数据
Object o = T1.threadLocal1.get();
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("在T2DAO的update()线程是=" + name + ",取出dog=" + o);
}
}
同一个线程中,取出的对象确实是同一个
ThreadLoad源码分析
ThreadLoad类图分析
可以看出 ThreadLoad 下有一个 静态类部类 ThreadLocalMap(ThreadLocal.ThreadLocalMap
)
ThreadLocalMap 有一个属性 table 为 Entry 数组
ThreadLocalMap 下有一个 静态类部类 Entry(ThreadLocal.ThreadLocalMap.Entry
)
Thread属性 threadLocals分析
在Thread类中,有一个属性 threadLocals ,它的类型是 ThreadLocal.ThreadLocalMap
默认为 null
Dbug查看set方法
threadLocal1 对象 = {ThreadLocal@547}
Thread0 的 threadLocals 属性 下的 table 下 目前只有两个默认的值
点击下一步进入数据
Thread0 的 threadLocals 属性 下的 table 下 加入了 一个数据
查看table表中的数据,可以发现
将当前ThreadLocal 和 传入的数据 存放到了 table 中
table表 为 Entry 数组 , 由此得出 set方法是将 当前threadlocal 和 传入的数据 为一对数据 存放到 entry 中
set方法源码分析
- 1. 进入set方法后,先获取当前线程
Thread t = Thread.currentThread()
- 2. 通过线程,获得ThreadLocalMap
ThreadLocalMap map = getMap(t);
- 2.1 获得map方法,本质就是 返回当前线程的threadLocals属性
return t.threadLocals
- 2.1 获得map方法,本质就是 返回当前线程的threadLocals属性
- 3. 判断
map
是否为空- 3.1 如果map不为空。将当前 threadlocal 和 value 以 entry 的形式 保存在 ThreadLocalMap 的 table 中
- 3.2 如果map为空。将创建 ThreadLocalMap
createMap(t, value);
- 3.2.1 创建ThreadLocalMap,本质就是创建一个ThreadLocalMap 并赋值给 当前的线程的 threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
- 3.2.2
new ThreadLocalMap(this, firstValue)
,本质就是给ThreadLocalMap 的 table 属性分配空间,并将 当前 ThreadLocal 和 value 以 Entry 的形式保存在table中
- 3.2.1 创建ThreadLocalMap,本质就是创建一个ThreadLocalMap 并赋值给 当前的线程的 threadLocals
通过上面分析:
- ThreadLocalMap 保存在 当前的线程的 threadLocals 属性中,这两的类型都是
Threadlocal.ThreadLocalMap
,当创建完ThreadLocalMap 都会 保存在当前线程中 - ThreadLocal只能保存一个数据,因为在保存数据时,是以ThreadLocal为key,key重复是会替换value的
- 当前线程下有一个
threadLocals属性
- threadLocals 属性 本质就是 threadLocal 下的静态类部类
ThreadLocalMap
,每次创建ThreadLocalMap
都会赋值给 threadLocals 属性 - ThreadLocalMap 下 有一个 table属性,table属性 本质就是
Entry数组
- ThreadLocalMap 下 有一个 静态类部类
Entry
。Entry只能存放一对数据,key 为当前ThreadLocal,value 为传进来的数据 并 存放到 table属性中(通过当前 ThreadLocal 的 hashcode 计算出 存放位置)
public void set(T value)
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//通过当前线程获取 当前线程对应的map
ThreadLocalMap map = getMap(t);
//如果map不为空,将当前threadlocal为key,传进来的数据为value放入map中
// 从这可以看出 一个threadlocal只能存放一个value,如果对threadlocal多次set,就会替换value
if (map != null)
map.set(this, value);
//如果map为空(第一次存放数据), 创建与当前线程对应map,
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
//返回当前线程的threadLocals
return t.threadLocals;
}
void createMap(Thread t, T firstValue)
void createMap(Thread t, T firstValue) {
//创建一个与当前线程有关的ThreadLocalMap对象(静态内部类)
// 并赋值给当前线程的threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建一个Entry数组table,默认大小为16
table = new Entry[INITIAL_CAPACITY];
//根据hash值计算存放位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//以当前threadlocal为key,传入的数据为value创建Entry 放入 table中
table[i] = new Entry(firstKey, firstValue);
size = 1;
//判断是否需要对table扩容
setThreshold(INITIAL_CAPACITY);
}
get方法源码分析
- 1. 进入get方法后,先获取当前线程
Thread t = Thread.currentThread();
- 2. 通过当前线程,获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
- 2.1 获取ThreadLocalMap,本质就是返回当前线程的 threadLocals 对象
return t.threadLocals;
- 2.1 获取ThreadLocalMap,本质就是返回当前线程的 threadLocals 对象
- 3. 判断
map
是否为空- 3.1 如果 map 不为空。通过当前 ThreadLocal 获得对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
- 3.1.1
map.getEntry(this);
,本质就是 通过 当前 ThreadLocal 的 hashcode 计算出 当前 ThreadLocal 存放在 table 中的 位置 并 取出 在该位置的 Entry-->
Entry如果不为空 并 Entry的key值与当前 ThreadLocal 相等-->
返回Entry-->
否则返回null
- 3.1.1
- 3.2 map为空。返回null
- 3.1 如果 map 不为空。通过当前 ThreadLocal 获得对应的Entry
先获取线程 -->
通过线程获得ThreadLocalMap
(threadLocals)-->
通过 ThreadLocalMap 和 当前 ThreadLocal 获得 对应 Entry -->
通过Entry 获得 value
public T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//通过当前线程,获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null) {
//通过当前 threadlocal获得对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//如果Entry不为空
if (e != null) {
//返回当前 threadlocal对应的 value
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//进行初始化,返回null
return setInitialValue();
}
ThreadLocalMap getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
//返回 当前线程的 threadLocals 属性
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key)
private Entry getEntry(ThreadLocal<?> key) {
//通过threadlocal的HashCode 计算出当前threadlocal 存放在 table 的位置
int i = key.threadLocalHashCode & (table.length - 1);
//取出 当前threadlocalHashCode 对应的 Entry
Entry e = table[i];
//Entry对象不为空,且 Entry的key 为 当前 ThreadLocal,返回Entry
if (e != null && e.get() == key)
return e;
else //返回null
return getEntryAfterMiss(key, i, e);
}