目录java
建立一个线程本地变量。程序员
返回此线程局部变量的当前线程副本中的值,若是这是线程第一次调用该方法,则建立并初始化此副本。数据库
返回此线程局部变量的当前线程的初始值。最多在每次访问线程来得到每一个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。若是线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。api
若该实现只返回 null;若是程序员但愿将线程局部变量初始化为 null 之外的某个值,则必须为 ThreadLocal 建立子类,并重写此方法。一般,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。安全
移除此线程局部变量的值。这可能有助于减小线程局部变量的存储需求。session
将此线程局部变量的当前线程副本中的值设置为指定值。多线程
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进行管理。ide
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就跟加锁完要解锁同样,用完就清理。