java中ThreadLocal的实现及原理

疑问: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中元素的键为线程对象,而值对应线程的变量副本。

实现例子:

9decbee297bd5ad166f6a739e3ce8c2c7b6.jpg

8aa7cb778182932eecf5322658d12013a30.jpg

 

运行结果:

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

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

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

6c48f2ee4124ab4a9518a017c5810e7fbcd.jpg

bf4bf178c5ad9c13a05a585035acf7e61f8.jpg

fc09ca2063ebe788c6a29202836c8680253.jpg

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

ThreadLocal的几个问题

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

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

2、ThreadLocal的内存泄露问题

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

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

如图,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是个很好的编码习惯,这样是不会引起内存泄漏。

https://segmentfault.com/a/1190000015718453

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则用于线程间的数据隔离。

转载于:https://my.oschina.net/u/4034553/blog/3068199

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaThreadLocal是一种线程本地存储机制,它可以让每个线程都拥有自己的变量副本,从而避免了线程安全问题。ThreadLocal实现原理是通过为每个线程创建一个独立的变量副本来实现的。具体来说,每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map,其键就是ThreadLocal,值可以是任意类型。当需要获取ThreadLocal的值时,ThreadLocal会首先获取当前线程,然后从当前线程的ThreadLocalMap获取对应的值。由于每个线程都有自己的ThreadLocalMap,因此不同线程之间的变量互不干扰。 以下是一个简单的示例代码,演示了如何使用ThreadLocal实现线程本地存储: ```java public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { int num = threadLocal.get(); for (int i = 0; i < 5; i++) { num++; threadLocal.set(num); System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get()); } } }; Thread t1 = new Thread(task, "Thread-1"); Thread t2 = new Thread(task, "Thread-2"); t1.start(); t2.start(); } } ``` 输出结果为: ``` Thread-1 : 1 Thread-1 : 2 Thread-1 : 3 Thread-1 : 4 Thread-1 : 5 Thread-2 : 1 Thread-2 : 2 Thread-2 : 3 Thread-2 : 4 Thread-2 : 5 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值