java并发编程之ThreadLocal

时间:2019-11-05 17:29:41   收藏:0   阅读:81

ThreadLocal是什么?

API

1. ThreadLocal()

创建一个线程本地变量。

2. T get()

返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。

3. protected T initialValue()

4. void remove()

移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。

5. void set(T value)

将此线程局部变量的当前线程副本中的值设置为指定值。

ThreadLocal使用示例


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

ThreadLocal源码分析

1. public void set(T value)

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进行管理。

2. public T get()

    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需要注意的有两点:

  1. ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key。
  2. 是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中,有些小伙伴会弄错他们的关系。

ThreadLocal的应用场景

  1. 最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等
/**
 * 数据库连接管理类
 */
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) {
            // 异常处理
        }
    }
}
  1. Hiberante的Session 工具类HibernateUtil
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对象以及和其相关的引用:
技术分享图片

可以看出,以用有两个引用
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。但是这些被动的预防措施并不能保证不会内存泄漏:

ThreadLocal 最佳实践

综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

总结

原文:https://www.cnblogs.com/xucoding/p/11798976.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!