java小名_面试题----java

ThreadLock

不同的线程,可以使用同一个ThreadLocal,同一个线程需要使用不同的ThreadLoca

简单使用

static final ThreadLocal sThreadLocal = new ThreadLocal();

sThreadLocal.set()

sThreadLocal.get()

threadlocal的作用负责访问和维护ThreadLocalMap.

ThreadLocalMap是每一个线程内部用于存储数据的地方。

set()方法就是把Threadlocal插入当前Thread维护的ThreadLocalMap中,如果你在线程1里面去set(),那么你在线程2里面去获取的时候,它取得是线程2的ThreadLocalMap,但是线程2里面的ThreadLocalMap并没有插入ThreadLocal.

我们看一下Thread类中的一个成员变量

public class Thread implements Runnable {

ThreadLocal.ThreadLocalMap threadLocals = null;

}

实际上ThreadLocal会获取当前线程的threadLocals,如果值为null,就实例化,如果值不为null,就在threadLocalMap中进行设置值。保证了值设置到对应的线程中。

我们看一下ThreadLocal源码

//set 方法

public void set(T value) {

//获取当前线程

Thread t = Thread.currentThread();

//实际存储的数据结构类型

ThreadLocalMap map = getMap(t);

//如果存在map就直接set,没有则创建map并set

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

//getMap方法

ThreadLocalMap getMap(Thread t) {

//thred中维护了一个ThreadLocalMap

return t.threadLocals;

}

//createMap

void createMap(Thread t, T firstValue) {

//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

threadLocalMap是一个类,threadLocalMap维护了一个Entry[] table,每一个数组元素是一个entity(保存threadLocal,value等),我们使用threadLocal.set(value),只能set一次,那为什么我们不使用entity,要使用数组呢?

1、首先这个线程不光光你的代码要用来存放value,其他框架spring也要用这个线程存放value,如果threadLocalMap只用entity对象存储,你进行set值的时候,就会把spring设置的值给覆盖掉。所以线程使用数组来存放各个框架设置的value。

2、不同框架使用自己的ThreadLocal,利用ThreadLocal将value存放到线程ThreadLocalMap中的不同的位置。原理:利用 int i = threadLocal.threadLocalHashCode&(len-1),i就是数组对应的位置。

d83a8159563586a2d8435fdcfd6c6636.png

我们不同的框架都可以利用线程存放自己的value,相互不影响

查看threadLocal.threadLocalHashCode返回的值

同一个threadLocal使用threadLocalHashCode值返回的都是同一个值,不同的ThreadLocal使用的threadLocalHashCode值不同,因为new ThreadLocal()的时候,已经将threadLocalHashCode自增了。所以不同的框架都new ThreadLocal(),生成的table索引就不一样,就可以存放到threadLocalMap不同的位置上了。

一个线程有很多ThreadLocal,不同的框架就可以用不同的的ThreadLocal保存数据。

//设置不可以变threadLocalHashCode,防止代码对它进行修改,属性调用方法只调用一次,调用的值进行赋值;

private int threadLocalHashCode = nextHashCode();

private static int nextHashCode() {

//自增1

return nextHashCode.getAndAdd(HASH_INCREMENT);

}

//static让所有的框架公用一个nextHashCode;不同的框架使用这个值,都会将他加自增1

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

ThreadLocalMap为什么要定义在ThreadLocal中(静态内部类),而不直接定义在Thread中?

将ThreadLocalMap定义在Thread类内部看起来更符合逻辑,但是ThreadLocalMap并不需要Thread对象来操作,所以定义在Thread类内只会增加一些不必要的开销。定义在ThreadLocal类中的原因是ThreadLocal类负责ThreadLocalMap的创建,仅当线程中设置第一个ThreadLocal时,才为当前线程创建ThreadLocalMap,之后所有其他ThreadLocal变量将使用一个ThreadLocalMap。

总的来说就是,ThreadLocalMap不是必需品,定义在Thread中增加了成本,定义在ThreadLocal中按需创建。

ThreadLock引发内存泄漏的问题

内存泄漏

指由于对象永远无法被垃圾回收导致其占用的JVM内存无法被释放。持续的内存泄漏会导致JVM可用内存减少,并最终可能导致JVM内存溢出(out of memory)直到JVM宕机。

5ce69972c9c4b822cb9a1fb66ea052cf.png

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.

所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

PS.Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。

由于ThreadLocalMap生命周期和Thread一样长,ThreadLocalMap中table中的entity由不同的ThreaLocal创建,如果ThreadLocal为null,但是entity中的值包括entity无法被回收,发内存泄漏。

解决方式:

调用threadlocal的remove方法.

threadlocal.set(T value)和get(T value)

set源码

private void set(ThreadLocal key, Object value) {

Entry[] tab = table;

int len = tab.length;

int i = key.threadLocalHashCode & (len-1);

#遍历table直到table[i]为空。

for (Entry e = tab[i];

e != null;

e = tab[i = nextIndex(i, len)]) {

ThreadLocal k = e.get();

#table[i].key是当前threadlocal,更新value,return;

if (k == key) {

e.value = value;

return;

}

#table[i].key如果是空,重新设置值,return;

if (k == null) {

replaceStaleEntry(key, value, i);

return;

}

}

#如果table[i]为空,正常设置值。

tab[i] = new Entry(key, value);

int sz = ++size;

if (!cleanSomeSlots(i, sz) && sz >= threshold)

rehash();

}

get源码

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

#如果获取不到,会设置一个空值,并返回

return setInitialValue();

}

private Entry getEntry(ThreadLocal key) {

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

#如果table[i].key等于当前key,返回

if (e != null && e.get() == key)

return e;

else

return getEntryAfterMiss(key, i, e);

}

private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {

Entry[] tab = table;

int len = tab.length;

往后一直遍历,找到table[i].key等于当前的key

while (e != null) {

ThreadLocal k = e.get();

if (k == key)

return e;

if (k == null)

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

return null;

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值