ThreadLocal常用操作及原理

概述

ThreadLocal并不是为了解决线程间共享变量的问题,而是提供线程内部共享变量,在多线程环境下可以保证各个线程之间的变量相互独立互不影响。可以通过set()/get()方法设置和获取元素值。

1、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();
}

分析:

  • 首先获取当前线程;
  • 然后获取当前线程的ThreadLocalMap记为map;
  • 如果map为null,调用setInitialValue()对线程的ThreadLocalMap进行初始化操作;
  • 如果map不为null,根据当前的ThreadLocal引用作为key,再map中获取对应的Entry,进而获取到value。

setInitialValue()方法

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

分析:

  • 首先调用initialValue()方法获取待存的值value,initialValue()倍protected修饰,一般在子类中复写,并设置值,如下:
    public class ThreadLocalTest {
    	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    		@Override
    		protected Integer initialValue() {
    			return Integer.valueOf(1);
    		}
    	};
    }
    
  • 然后获取当前线程的ThreadLocalMap;
  • 如果map不为空,就以当前ThreadLocal的引用作为key,value作为value设置map;
  • 如果map为空,就调用createMap()方法创建ThreadLocalMap
2、ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个静态内部类,每个Thread维护了一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal 的引用,value为真正存储的值,这样设计的好处在于首先map的Entry变小了,之前是Thread的数量,现在是ThreadLocal的数量,Thread销毁之后对应的ThreadLocalMap也会被销毁,减少了内存的使用量。

ThreadLocalMap有HashMap的部分特性,真正存储数据的是内部的Entry

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

由于Entry继承了弱引用,Entry构造函数使用了父类的构造函数,因此Entry的key为ThreadLocal的弱引用,值为value,引用一张用的很多的图,实线为强引用,虚线为弱引用
在这里插入图片描述
弱引用在下一次垃圾回收时会被回收,ThreadLocalMap中的Entry使用ThreadLocal的弱引用作为key,当外部不存在ThreadLocal的引用时,ThreadLocal在下次垃圾回收时就会被回收,此时再ThreadLocalMap中就出现了key为null的Entry,但由于Thread类维护了一个TheadLocalMap的映射表,所以这些key为null的Entry不会被回收直到Thread被销毁。

所以对于上面的情况当线程被回收时这些key为null的Entry自然也会被回收,这没什么问题。但当使用线程池时,核心线程一直存在,这些key为null的Entry就无法释放,就会造成内存泄漏。

ThreadLocalMap解决上面内存泄漏的方法如下:

  • 在get()方法里有这么一条:ThreadLocalMap.Entry e = map.getEntry(this);,来获取Entry;
  • getEntry()方法首先通过获取hash值找到ThreadLocal引用作为key的Entry位置,然后判断里面的key是否为null,若存在就返回其value,否则就调用getEntryAfterMiss()方法,采用线性探查的方式查找key的可能位置。
  • 在查找的过程中如果发现key为null,说明key被回收了,则调用expungeStaleEntry()方法清理key对应的value与Entry

在ThreadLocalMap中的set()/getEntry()方法中,都会调用expungeStaleEntry(int)方法,但是如果我们既不需要添加value,也不需要获取value,那还是有可能产生内存泄漏的。所以最好的防止内存泄漏的方式就是手动调用remove()方法来删除不再需要的ThreadLocal,可以将ThreadLocal设置为private static,在不需要的时候调用remove()方法删除,这样就可以防止内存泄漏。

3、SpringMVC是如何解决并发问题的

SpringMVC的controller默认时单例模式的singleton,也就是说每个请求过来SpringMVC都使用同一个instance来处理,当多个线程调用或者同时又多个请求过来时就变得不安全了,因此在使用SpringMVC时应避免创建实例变量。

如果控制器是使用单例形式,且controller中有一个私有的变量a,所有请求到同一个controller时,使用的a变量是共用的,即若是某个请求中修改了这个变量a,则,在别的请求中能够读到这个修改的内容。

Spring解决上述问题一般有两种方式

  • 再配置文件Controller中声明 scope=“prototype”,每次都创建新的controller;
  • 再Controller中使用ThreadLocal变量,如
ThreadLocal<String> s = newThreadLocal<String>();  定义一个ThreadLocal 变量
s.set(System.currentTimeMillis());   写入值
s.get();  读取值
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值