疑问:ThreadLocal可以解决共享变量的并发问题?
1、概念:

ThreadLocal类用来存放线程的局部变量,每个线程都有自己的局部变量彼此之间不共享。ThreadLocal<T>主要有一下三个方法:

(1).public T get():返回当前线程的局部变量。

(2).protected T initValue():返回当前线程的局部变量初始值。默认情况下initValue(),返回null。线程在没有调用set之前,第一次调用get的时候,get方法会默认去调用initValue这个方法,所以如果没有覆盖这个方法,可能导致get返回的是null。当然如果调用了set方法,结果就会不一样了。

(3).public void set(T value):设置当前线程的局部变量。

ThreadLocal是如何做到为每一个线程提供单独的局部变量呢?实际在ThreadLocal类中有一个Map缓存,用于存储每一个线程的局部变量。Map中元素的键为线程对象,而值对应线程的变量副本。

实现例子:

java线程变量自动释放 java线程变量怎么实现的_内存泄漏

java线程变量自动释放 java线程变量怎么实现的_局部变量_02

 

运行结果:

thread1:A1
thread1:B1
thread2:A2
thread2:B2

总结:从该例子中可看出多个线程设置ThreadLocal的值只有在该线程的作用范围内有效。操作ThreadLocal的set,get方法实际上是操作该线程的一个代理,其本质是在该线程的ThreadLocalMap中存储了key为ThreadLocal本身和对应的值。

隔离性:验证线程变量的隔离性

java线程变量自动释放 java线程变量怎么实现的_java线程变量自动释放_03

java线程变量自动释放 java线程变量怎么实现的_java线程变量自动释放_04

java线程变量自动释放 java线程变量怎么实现的_java_05

小结:从上面执行的结果可以看出,每一个线程向ThreadLocal中存值时,但是每个线程取出的都是自己线程的值,这也就是验证的线程变量的隔离性

ThreadLocal的几个问题

1、为什么不直接用线程id来作为ThreadLocalMap的key?

因为一个线程中可以有多个ThreadLocal对象,所以ThreadLocalMap中可以有多个键值对,存储多个value值,而如果使用线程id作为Key,那么就只有一个键值对了。

2、ThreadLocal的内存泄露问题

首先理解内存泄漏(memory leak)和内存溢出(out of memory)的区别。内存溢出是因为在内存中创建了大量引用的对象,导致后续再申请内存时没有足够的内存空间供其使用,内存泄漏是指程序申请完内存后,无法释放已经申请的内存空间。(不再使用的对象或者变量仍占内存空间)。

根据Entry方法的源码,我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为key的。下图是介绍到一些对象之间的引用关系图,实线表示强引用,虚线表示弱引用。

java线程变量自动释放 java线程变量怎么实现的_java_06

如图,ThreadLocalMap使用ThreadLocal弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry,如果当前线程迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链。

Thread Ref ->Thread->ThreadLocalMap->Entry->value

内存申请完之后,永远无法回收,造成内存泄漏。只有当前thread结束以后,Thread Ref就不会存在栈中,强引用断开,Thread,ThreadLocalMap,Entry将全部被GC回收。但如果是线程对象不被回收的情况,比如使用线程池,线程结束是不会被销毁的,就可能出现真正意义上的内存泄漏。

ThreadLocalMap设计时对上面问题的对策:

当我们仔细研究ThreadLocalMap的源码,可以推断,如果在使用ThreadLocal的过程中,显式的进行remove是个很好的编码习惯,这样是不会引起内存泄漏。

3、ThreadLocal使用场景举例

ThreadLocal既然不能解决并发问题,那么它适用的场景是什么呢?

ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

(1)、获取Session。

(2)、多数据源下的数据库连接。



{spring与ThreadLocal

spring中的bean大多都是单例的,但是我们应用又是支持并发的,所以将各个service,dao存放在ThreadLocal中是一个明智的做法。但是需要注意如果bean中包含共享变量,spring是没有做任何的安全处理的。


【synchronized与ThreadLocal

ThreadLocal以空间换取时间,提供了一种非常简便的多线程实现方式。 
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别:
synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。