五、ThreadLocal

使用场景:
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

不完整。。待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值