ThreadLocal
1、ThreadLocal作用
提供线程内的局部变量,不同的线程之间不会互相干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间的一些公共变量传递的复杂度
总结下来就是三个词:
线程并发:在多个线程情况下
传递数据:同一线程,不同组件中传递公共变量
线程隔离:每个线程的变量都是独立的,不会互相影响
2、基本使用
1、set:设置当前线程绑定的局部变量
2、get:获取当前线程绑定的局部变量
3、remove:删除当前线程绑定的局部变量
3、代码演示
使用了ThreadLocal后,set和get只会从当前线程中获取值,而不会跑去别的线程中获取值
//ThreadLocal只会从当前线程中获取值,而不会去其他线程中获取值
public ThreadLocal<String> contentLocal = new ThreadLocal<>();
private String content;
public String getContent() {
// return content; //优化前
return contentLocal.get(); //优化后
}
public void setContent(String content) {
// content = content; //优化前
contentLocal.set(content); //优化后
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
demo1.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "的数据为:" + demo1.getContent());
},"thread" + i).start();
}
}
4、使用场景
在进行对象传递的时候,可以避免传参传递,而使用threadLocal来进行传递数据
例如使用数据库连接池时,一个线程只需要一个conn,所以直接使用threadLocal来判断当前线程是否已经获取到了,如果获取到不需要再创建,直接get即可
public final class ConnectionUtil {
private ConnectionUtil() {}
private static final ThreadLocal<Connection> conn = new ThreadLocal<>();
public static Connection getConn() {
//threadLocal中获取链接
Connection con = conn.get();
if (con == null) {
//如果没有,则创建 下次可直接获取到
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("url", "userName", "password");
conn.set(con);
} catch (ClassNotFoundException | SQLException e) {
// ...
}
}
return con;
}
}
5、源码设计
每个thread维护一个threadLocalMap,这个map的key是threadLocal实例本身,value是要存储的值
1、set方法:这里就可以看出,传入的是线程,根据线程创建一个key为threadLocal,value为值的一个threadLocalMap,如果线程内已经有值,则覆盖
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
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建一个threadlocal为key,值为value 的map
void createMap(Thread t, T firstValue) {
//这里使用的是this为key,也就是当前对象threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
2、get:根据线程获取到ThreadLocalMap的value
public T get() {
Thread t = Thread.currentThread();
//根据线程获取到ThreadLocalMap的value
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
6、内存泄漏
首要明确:使用threadLocal时,需要注意threadLocal中的Entry继承了弱引用对象,当下次垃圾回收时,会将该对象清理的key清理。
1、概念
1、内存溢出:是指内存不足
2、内存泄漏:是指已分配的堆内存某种原因无法释放或没释放,造成系统内存浪费,导致系统运行缓慢或者崩溃
2、 为什么要使用弱引用
因为弱引用会在gc时清除,也就给我们提供了一层保障,也就是即使没有调用remove,至少key被清理了,但如果使用强引用,如果不执行remove,则key肯定该不会被清理
3、内存泄漏的原因
即使弱引用会帮我们清除key,但是此时value和threadLocalMap对象还是被强引用着,也就不会被gc清理,这个才是真正造成垃圾回收的原因。
4、如何解决内存泄漏
养成良好的编码习惯,使用完threadLocal后一定要执行remove方法,remove内部源码为 如果发现key为null,则将entry对象改为null