Java多线程笔记(9)-多线程补充
ThreadLocal
概述
Java中提供ThreadLcoal,用来实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题。它能够达到线程内的资源共享,线程间的资源隔离的效果。ThreadLocal提供的变量在多线程环境下访问时能保证各个线程的变量相对独立于其他线程内的变量,分配在堆内的TLAB(本地线程分配缓冲,Thread Local Allocation Buffer)中。
在每个线程需要自己单独实例,实例需要在线程内的多个方法中共享,但又不希望多线程共享的时候,我们可以使用ThreadLocal。
ThreadLocal的常用方法:
方法 | 描述 |
---|---|
ThreadLocal<>() | 创建 ThreadLocal 对象 |
protected T initialValue() | 返回当前线程局部变量的初始值 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
应用举例:
1 |
|
输出如下,这里每个线程都调用了Util的同一个静态方法获取资源,但是得到的资源是各不相同的
1 |
|
原理
在每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象。真正起到线程隔离作用的是这个ThreadLocalMap,而ThreadLocal起到的作用是用来关联ThreadLocalMap和实际对象:
- 调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
- 调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
- 调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
在ThreadLocalMap中,key即ThreadLocal被设计为弱引用。有如下的原因:
- Thread可能需要长时间运行,例如线程池中的线程。如果我们的key不再使用,我们希望能够释放掉它的内存。如果使用强引用,那么就无法释放掉这个key对应的内存空间,即使除了ThreadLocalMap的其他地方都没有引用这个key
- 虽然key是弱引用,但是value仍然是强引用。GC能够让key的内存释放,但是后续还需要根据key是否为null来进一步释放值的内存,释放时机如下:
- 获取key的时候发现key为null
- set key的时候,使用启发式扫描,清除临近的null key,启发次数与元素个数,是否发现null key有关
- 调用remove的时候,相当于手动移除,这也是最推荐的一种方式
但是一般在使用的时候,我们会将ThreadLocal作为静态变量,这样key就始终保持引用,无法被释放,对应的value也会始终占用空间,导致内存泄漏的问题
ThreadLocalMap是ThreadLocal的内部类,它没有实现Map接口,而是用独立的方式实现了Map的功能,内部的Entry也是独立实现。
关于Entry,它继承了WeakReference,其中的key是弱引用,并且限制只能使用ThreadLocal作为key。key为null意味着key不再被引用,Entry也可以从table中清除。关于Map,当元素个数大于等于长度的一般,进行扩容,扩容到原来的两倍。进行元素的添加的时候,使用开放定址法来解决冲突,使用的探测序列为线性序列。