既然写了博客我就写的详细一些,尽量易懂一些(反正我也操作了,哈哈),以后慢慢的就不会了呦
来上才艺了哈:学习、认识三部曲:
1. ThreadLocal是什么
2. ThreadLocal的应用
3. ThreadLocal怎么实现的
4. ThreadLocal有没有问题(内存泄漏)
5. 融会贯通
那我们开始吧
ThreadLocal是什么
当程序每新启动一个线程(web应用上包括用户访问),ThreadLocal会依据你当前的线程给你保存一些信息,仅仅是和你当前线程相关。
ThreadLocal的应用
既然可以携带和你当前线程有关的信息,那是不是我可以把每个访问线程都放到里面,就方便存取了呢,哪些应用可以实现(之前接触的有放入用户信息的),做的最多的可能就是去操作数据,里面放的都connection信息,这样操作就相当于每个线程只建立一个连接。给你个示例代码自己先看看
public class DBUtil {
// 数据库配置
private static final String driver = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/demo";
private static final String username = "root";
private static final String password = "root";
// 定义一个用于放置数据库连接的局部线程变量(使每个线程都拥有自己的连接)
private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();
// 获取连接
public static Connection getConnection() {
Connection conn = connContainer.get();
try {
if (conn == null) {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.set(conn);
}
return conn;
}
// 关闭连接
public static void closeConnection() {
Connection conn = connContainer.get();
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.remove();
}
}
}
ThreadLocal怎么实现的
既然已经知道获取的是当前线程相关的那就解析一下这个过程。先上类图:
应该先看 Thread 里面:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals 是一个map 没错就是它,它保存的是你的当前线程和你对应的value,对应的
ThreadLocalMap 是ThreadLocal的内部类。这个就说明了,我把它交给了ThreadLocal去管理。
那现在就得看它的初始化了,一开始它做了什么:
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
当然了有些会重写它的初始化,在获取的时候,这样就可以直接赋值了。
看到了吧 threadLocals 的key 就是 当前的ThreadLocal。不行再看下set 和 get 哈
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我已经尽力了。
简单的说就是:
Thread.threadLocals(map) ------>ThreadLocal.ThreadLocalMap(ThreadLocal为键,注意ThreadLocalMap是个静态class)------>set、get 调用初始化赋值
ThreadLocal有没有问题(内存泄漏)
Thread---->ThreadLocal.ThreadLocalMap
其实还是map中的内存泄漏问题。map中把theadlocal 变成null,GC之后在map中存储的弱引用对象的hashcode来作为key,那么它就被回收了,但是它的value一直就被放在堆里面,并且被当前的线程threadLocals引用,因为在线程池里的他可能一直都不释放,导致内存泄漏了。 所以导致内存泄漏的情况需要满足:
- 触发了垃圾回收
- 没有调用get、set、remove因为如果调用这个threadlocal 会发现键为空了,会重新赋值
- 线程一直运行
所以解决方案:
最简单有效的方法是使用后将其移除 记得用remove
过程大家可以参考这个我觉得还没他写的好
ThreadLocal理解及应用
我们来分析一下这个mat图:
这个视图呢,是分析存活的最大空间占用的,在每一列的头部可以正则搜索,我们搜索
这个是分析对象实例个数和占用的
这个工具还可以分析多个dump文件来进行对比分析
JVM故障分析及性能优化
然后搜索发现没有key的引用,只有value的空间占用很大。
验证了内存泄漏的问题