ThreadLocal
ThrteadLocal来提供线程内局部变量,这种变量在多线程访问时,可以保证每个线程变量互相独立.
这种变量在线程生命周期内起作用,可以在不同组件中传递,类似于Javaweb 的Context 域.
常用方法:
方法 | 说明 |
---|---|
ThreadLocal<?>() | 创建对象 |
set(T value) | 设置当前线程绑定的局部变量 |
T get() | 获取变量 |
remove() | 移除与当前线程绑定的变量 |
Thread 与Synchornized的区别:
相同点:
两者都是用于处理多线程并发访问下的问题.
不同点:
Sychronized:
同步锁采用时间换空间的方式,只提供一份变量,让不同线程排队访问.
是多个线程之间访问资源的同步.
ThreadLocal:
采用以空间换时间的方式,为每一个线程都提供一份变量副本,从而实现同时访问,互不干扰.
多线程中每个线程之间数据相互隔离,具有高并发性.
ThreadLocal解决数据库连接并发的问题:
转账案列:
常规解决方案:使用synchorized关键字
注意:
1,service层和dao层连接对象保持一致,
2,每个线程Connection对象必须前后一致.
伪代码:
5,加锁:
synchronized(thsi.class){
1,获取连接并开启事务:
Connection conn =JdbcUtils.getConnection();
coon.setAutoCommit(false);
2,转账操作:
dao.out(outUser,money,conn);
3,产生异常:
int i/0;
dao.in(inUser,money,conn);
4 ,提交事务并关闭:
JdbcUtils.setCommitAndClose();
处理异常:
conn.rollback;
}
#####常规方案的缺陷:
1,connection对象从service层传递connection到dao层, 造成代码耦合度提高,
2,加锁会造成线程失去并发性,程序性能降低
使用ThreadLaocal解决方案:
1,直接获取当前线程绑定的conn对象:
为空: 从连接池中获取,在将此连接绑定到当前线程.
2,在关闭连接或者回滚时,需要释放当前线程绑定的连接对象!
伪代码:
jdbcUtils:
常规获取连接方式:
datasource.getConnection();
使用threadlocal获取方式:
public class JdbcUtils { //ThreadLocal对象 : 将connection绑定在当前线程中 private static final ThreadLocal<Connection> tl = new ThreadLocal(); // c3p0 数据库连接池对象属性 private static final ComboPooledDataSource ds = new ComboPooledDataSource(); // 获取连接 public static Connection getConnection() throws SQLException { //取出当前线程绑定的connection对象 Connection conn = tl.get(); if (conn == null) { //如果没有,则从连接池中取出 conn = ds.getConnection(); //再将connection对象绑定到当前线程中 tl.set(conn); } return conn; } //关闭连接时需要释放当前线程绑定的连接对象: t1.remove(); conn.close();
该方案的好处:
- 传递数据 :保存每个线程绑定的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题
- 线程隔离 : 各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失
ThreadLocal结构:
jdk早期设计:
每个ThreadLocal都创建一个Map,
key ->线程
value -> 线程变量
jdk8中的设计:
每个Thread都维护一个Map,
Thread{
//成员属性:
ThradLocalMap threadLocals;
}
key ->threadlocal实例
value -> 线程变量
设计好处:
1, 每一个map存储的entry减少,可以减少hash冲突.
2,当线程销毁时,ThreadLocalMap 也会随之销毁,减少内存使用.
ThreadLocal内部结构:
1.set():
/**
* 设置当前线程对应的ThreadLocal的值
*
* @param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
A. 首先获取当前线程,并根据当前线程获取一个Map
B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
C. 如果Map为空,则给该线程创建 Map,并设置初始值
2,get:
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
@SuppressWarnings("unchecked")
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
/*
初始化 : 有两种情况有执行当前代码
第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
*/
return setInitialValue();
}
/**
* 初始化
*
* @return the initial value 初始化后的值
*/
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
// 返回设置的值value
return value;
}
A. 首先获取当前线程, 根据当前线程获取一个Map
B. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则转到D
C. 如果e不为null,则返回e.value,否则转到D
D. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map
总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。
3,remove()
public void remove() { // 获取当前线程对象中维护的ThreadLocalMap对象 ThreadLocalMap m = getMap(Thread.currentThread()); // 如果此map存在 if (m != null) // 存在则调用map.remove // 以当前ThreadLocal为key删除对应的实体entry m.remove(this); }
4.initialValue():
protected T initialValue() {
return null;
}
此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时
- 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
- 通常情况下,每个线程最多调用一次这个方法。
- 这个方法仅仅简单的返回null
- 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,必须通过子类继承的方式去重写此方法,通常, 可以通过匿名内部类的方式实现
ThreadLocalMap源码分析
ThreadLcalMap是Thread的静态内部类
ThreadLcalMap{
initalCapacity :初始容量;
table Entry[] :存放entry的数组;
threshold: 数组扩容的阈值;
size: 数组里面的entry的个数;
}
Entry:
entry是**弱引用,**其目的是将ThreadLocal对象生命周期和线程生命周期绑定.
弱引用(weekReference):
垃圾回收器一旦发现只有弱引用的对象,不管当前内存是否足够都会回收他的内存.
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
内存分析图:
关于内存泄露的相关问题:
内存溢出memory overflow: 没有足够的内存提供给申请者使用.
内存泄漏 momory leak : 程序中动态分配的内存由于某种原因,内存未释放或者无法释放,造成内存的浪费,导致程序运行速度过慢,甚至系统崩溃等后果.内存泄露的堆积将导致内存溢出.
内存泄露与key的弱引用还是强引用没有关系.
出现内存引用的真实原因:
- 1,没有手动删除entry
- 解决:调用remove方法
- 2,currentLocal任然运行:
- 解决:ThreadLocalmap(entry)是Thread的一个属性,被当前线程所引用,只要当前线程结束,entry会被回收,从根源上避免内存泄漏.
为什么还要使用弱引用?
- 因为避免内存泄露的第二种方式不好控制,特别是使用线程池时候,线程结束不会被销毁.
- 事实上在ThreadLocalMap中的get/set/remove方法中对key(threadLocal)为null,会将value也会设置为null.
结论:
使用完ThreadLocal,currentThread依然在运行状态下,弱应用的threadLocal会被回收,只要下次ThreadLocalMap调用set/get/map 任意方法,value会被清除,从而避免内存泄露.