使用场景:
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
例如多线程中SimpleDateFormat中的格式化,它是非线程安全的,使用肯定是不妥;如果加锁呢?那必定会影响效率,每次创建实例也是一样的。
可以通过ThreadLocal为每个线程存储实例对象,只给这条线程使用。
ThreadLocal导致内存泄漏?
Thread中的方法
get() 返回此线程局部变量的当前线程副本中的值。
set() 将此线程局部变量的当前线程副本中的值设置为指定值。
remove() 移除此线程局部变量当前线程的值。
initialValue() 返回此线程局部变量的当前线程的“初始值”。
代码示例:
/**
* @description: ThreadLocal 操作例子
* @author: MR.CHEN
* @create: 2018-12-10 11:15
**/
public class ThreadAttrAdnMethod {
public static void main(String[] arg){
Thread[] threads=new Thread[3]; //初始化线程组
for(int i=0;i<3;i++){
threads[i]=new Thread(String.valueOf(i)){
@Override
public void run() {
System.out.println("线程:"+this.getName()+"\t"+"获取的值:"+threadId.get());
System.out.println("线程:"+this.getName()+"\t"+"获取的值:"+threadId.get());
threadId.remove(); //手动销毁
}
};
threads[i].start();
}
}
static class threadId<T>{
private static final AtomicInteger atomicInteger=new AtomicInteger(0);
private static final ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return atomicInteger.getAndIncrement(); //标记:不会因为它而变大
}
};
public static final Integer get(){
return threadLocal.get();
}
public static void set(Integer integer){
threadLocal.set(integer);
}
public static void remove(){
threadLocal.remove();
}
}
}
结果:
线程:1 获取的值:0
线程:2 获取的值:2
线程:0 获取的值:1
线程:2 获取的值:2
线程:1 获取的值:0
线程:0 获取的值:1
总结:结果执行的第二遍获取线程里的值还是跟第一遍一样的,没有因为“atomicInteger.getAndIncrement()”而变大,
也不会因执行顺序其他线程去改变数值。由此可以得出每个线程的ThreadLocal变量都是私有的
######ThreadLocal分析下源码
ThreadLocal<T>下
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //可以看到是获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value); //当前线程不为NULL时直接将ThreadLocal实例作为Key。
else
createMap(t, value); //为当前线程创建一个。
}
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //从下面代码中可以看到返回了一个ThreadLocalMap,再看Thread类,获取的是当前线程的ThreadLocal类型的threadLocals属性。显然变量副本存储在每一个线程中。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //根据实例对象取出Entry(ThreadLocal<?> k, Object v)对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//为当前线程创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Thread类下
ThreadLocal.ThreadLocalMap threadLocals = null;
上面我们知道变量副本存放于何处,这里我们简单说下如何被java的垃圾收集机制收集,当我们不在使用时调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操作,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。
来自:https://blog.csdn.net/moshenglv/article/details/52771160
缺点:
因为强弱引用导致存泄漏,消耗资源量大
ThreadLocalMap:
ThreadLocal不需要key,因为线程里面自己的ThreadLocal.ThreadLocalMap不是通过链表法实现的,而是通过开地址法实现的
每次set的时候往线程里面的ThreadLocal.ThreadLocalMap中的table数组某一个位置塞一个值,这个位置由ThreadLocal中的threadLocaltHashCode取模得到,如果位置上有数据了,就往后找一个没有数据的位置,每次get的时候也一样,根据ThreadLocal中的threadLocalHashCode取模,取得线程中的ThreadLocal.ThreadLocalMap中的table的一个位置,看一下有没有数据,没有就往下一个位置找。既然ThreadLocal没有key,那么一个ThreadLocal只能塞一种特定数据。如果想要往线程里面的ThreadLocal.ThreadLocalMap里的table不同位置塞数据 ,比方说想塞三种String、一个Integer、两个Double、一个Date,请定义多个ThreadLocal,ThreadLocal支持泛型"public class ThreadLocal"。
存储位置算法:
1、空间位置的计算
Hash增量设置为0x61c88647,也就是说ThreadLocal通过取模的方式取得table的某个位置的时候,会在原来的threadLocalHashCode的基础上加上0x61c88647
深入可参考:
https://blog.csdn.net/moshenglv/article/details/52771160
https://www.cnblogs.com/hongshijie/p/9419387.html
总结:
1、ThreadLocal不是集合,它不存储任何内容,真正存储数据的集合在Thread中。ThreadLocal只是一个工具,一个往各个线程的ThreadLocal.ThreadLocalMap中table的某一位置set一个值的工具而已
2、同步与ThreadLocal是解决多线程中数据访问问题的两种思路,前者是数据共享的思路,后者是数据隔离的思路
3、同步是一种以时间换空间的时间的思想,ThreadLocal是一种空间换时间的思想
4、ThreadLocal既然是与线程相关的,那么对于Java Web来讲,ThreadLocal设置的值只在一次请求中有效,是不是和request很像?因为request里面的内容也只在一次请求有效,对比一下二者的区别:
***(1)ThreadLocal只能存一个值,一个Request由于是Map形式的,可以用key-value形式存多个值
(2)ThreadLocal一般用在框架,Request一般用在表示层、Action、Servlet
扩展:
JAVA的回收机制 弱引用、强引用等:https://www.cnblogs.com/zjxynq/p/5882877.html 相关ThreadLocal 的BUG 内存泄漏
示例全局MAP内存泄漏:https://www.ibm.com/developerworks/cn/java/j-jtp11225/
引用类型:
1强引用:在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
2软引用:软引用需要用SoftReference类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
3弱引用:弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
4虚引用:虚引用需要PhantomReference类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。
复合操作和原子操作:
nextId++这种操作是个复合操作而非原子操作
hash 开放定址算法:
希腊字母:λ Lambda
不完整。。待续…