目录
创建一个线程本地变量。
返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。
将此线程局部变量的当前线程副本中的值设置为指定值。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(){
@Override
public void run(){
System.out.println("当前线程: " + Thread.currentThread().getName() + ", 已分配id: " + ThreadId.get());
}
}.start();
}
}
static class ThreadId{
private static final AtomicInteger id = new AtomicInteger(0);
private static final ThreadLocal<Integer> local = new ThreadLocal<>(){
@Override
protected Integer initialValue(){
return id.getAndIncrement();
}
};
// 返回当前线程的唯一的序列,如果第一次get,会先调用initialValue,后面看源码就了解了
public static int get(){
return local.get();
}
}
}
控制台打印结果:
当前线程: Thread-2, 已分配id: 1
当前线程: Thread-0, 已分配id: 2
当前线程: Thread-3, 已分配id: 4
当前线程: Thread-1, 已分配id: 0
当前线程: Thread-4, 已分配id: 3
public void set(T value) {
Thread t = Thread.currentThread(); // 取当前线程
ThreadLocalMap map = getMap(t); // 和当前线程关联的Map对象
if (map != null) {
map.set(this, value); // this是当前ThreadLocal对象,将value映射到和当前线程相关的Map中
} else {
createMap(t, value); // 不存在则创建
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到ThreadLocalMap是Thread对象中的一个属性。每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用.所以每个线程都会有一个ThreadLocal.ThreadLocalMap对象的引用。
通俗的讲,每一个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量(作为线程私有变量,从而也是线程安全的),而这些成员变量可以代理给ThreadLocal进行管理。
public T get() {
Thread t = Thread.currentThread();
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(); // 如果Map中已经存在值,不管是set()方法设置,还是已经初始化过,都不再调用
}
对于ThreadLocal需要注意的有两点:
/**
* 数据库连接管理类
*/
public class ConnectionManager {
/** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
public static Connection getCurrConnection() {
// 获取当前线程内共享的Connection
Connection conn = threadLocal.get();
try {
// 判断连接是否可用
if(conn == null || conn.isClosed()) {
// 创建新的Connection赋值给conn(略)
// 保存Connection
threadLocal.set(conn);
}
} catch (SQLException e) {
// 异常处理
}
return conn;
}
/**
* 关闭当前数据库连接
*/
public static void close() {
// 获取当前线程内共享的Connection
Connection conn = threadLocal.get();
try {
// 判断是否已经关闭
if(conn != null && !conn.isClosed()) {
// 关闭资源
conn.close();
// 移除Connection
threadLocal.remove();
conn = null;
}
} catch (SQLException e) {
// 异常处理
}
}
}
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义SessionFactory
static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 获取当前线程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
一张图来看一下ThreadLocal对象以及和其相关的引用:
可以看出,以用有两个引用
ThreadLocal ---> 堆对象
Current Thread ---> Thread ---> ThreadLocalMap ---> Entry ---> ThreadLocal ---> 堆对象
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。但是这些被动的预防措施并不能保证不会内存泄漏:
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。
综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
原文:https://www.cnblogs.com/xucoding/p/11798976.html