关于ThreadLocal的用法和原理这篇文章已经写挺好了:Java面试必问:ThreadLocal终极篇
ThreadLocal涉及到了java的四种引用这篇文章写了:理解Java的强引用、软引用、弱引用和虚引用
泄漏也可以参考一下这个文章里面的图不错这里拿过来展示下:ThreadLocal内存泄漏问题

内存泄漏
测试代码:
public class Test {
public static ThreadLocal<String> test = new ThreadLocal<>();
public static void main(String[] args){
Test.test.set("test");
Test.test = null;
System.gc();
System.out.println("1");
}
}
这里threadlocal的原理简单来说,就是Thread持有一个ThreadLocalMap的变量,然后在每一次threadLocal.get()或者threadLocal.set()的时候,回去判断这个threadlocal在当前县城threadlocal里面有没有,如果没有的话那么就创建一个Entry的数据结构,这个数据结构把Threadlocal这个对象包装成一个WeakReference的对象,然后value就设置到Entry的value字段里面。当GC活动的时候会先把弱引用的对象设置成null,然后回收掉这个对象。


然后再看demo里面,有一个静态变量,test首先把它set成了test,因为在属性里面设置了public static ThreadLocal<String> test = new ThreadLocal<>();这其实强引用,然后把它设置成了test=null,这个时候其实强引用关系就断了,只剩下ThreadLocalMap里面的Entry,包装成弱引用的ThreadLocal,这个时候运行GC就会回收掉这个ThreadLocal,但是在线程里面的Entry数组里面的Entry因为线程对象Thread没有释放还是存在的,包括里面存放的value的值也都存在的,如果这个时候用的是线程池的对象,那么这个时候线程不会被回收,如果一直循环使用线程,那么就会泄漏。

个人认为这个回收的的时候就是做GCRoots扫描的时候发现,这个实例包装了弱引用,然后有没有强引用引用它,那么JVM就会把弱引用的地方设置成null值,然后同时GC线程回收他。
实际使用
在项目中经常使用Threadlocal的变量来做上下文Context的作用,比如PageHelper里面就是利用设置ThreadLocal的变量来设置是否分页分页的页码等参数,然后再Mybatis的拦截其里面取出来,然后进行查询的。
在Spring里面事务管理,就是使用Threadlocal来设置Connection的连接和是否需要回滚setrollbackonly的变量,来实现事务的传播机制。
我自己在实现数据权限的过程中也是直接使用Threadlocal加mybatis拦截器的方式,来修改原始的sql语句来实现数据过滤。
在实际使用中其实还是要注意使用threadlocal.remove()方法来实现移除,避免线程复用的时候的内存溢出问题,这个方法也会把key为null的变量从ThreadLocalMap里面给移除掉。
其实一般在使用的时候都是public static final ThreadLocal<String> test = new ThreadLocal<>();的用法,这样的问题就是设置成final类型的变量,他连弱引用都不会移除,因为你不能设置test=null这样的语句,编译器不会过,所以更需要remove方法。