ThreadLocal而是一个java.lang 包下的线程内部的存储类,可以在线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,实现线程隔离。
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。且在线程内部任何地方都可以使用,线程之间互不影响
ThreadLocal 提供程内的局部变量,不同的线程之间不会相互程的生命周明内起作用,减少同一个程内多个函数或组件之间一些公共变量传详的复杂度
在多线程并发下我们可以通过过 Threadlocal在同一线程,不同组件中传通公共变量.每个线程的变量都是独立的,不会互相影响
内部结构
每个线程持有一个ThreadLocalMap对象。这个Map的key是 Threadlocal实例本身, valueオ是真正要存储的值 object每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
- 每个 Threads程内部都有一个Map( Threadlocalmap),thread 销毁的时候map也会销毁
- (Map里面存储 Threadlocall对象(key)和线程的变星副本( value)
- Thread内部的Map是由 Threadlocal维护的,由 Threadlocal负责向map获取和设置程的变量值
- 对于不同的线程,每次获取本值时,别的线程并不能取到当前线程的副本值,形成了副本的隔离互不干抗
空参数构造
- Threadlocal创建 Threadlocal对象
通过get和set方法就可以得到当前线程对应的值。
该方法会检查当前调用线程,默认以该线程的Thread.currentThread() 值作为键,来保存指定的值。将变量绑定到线程中
该方法会检查当前调用线程,默认以该线程的Thread.currentThread() 值作为键,获取线程绑定保存的指定值。
移除当前程绑定的局部变量
ThreadLocal主要用于: 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理
get方法,获取线程本地副本变量
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return 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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
set 方法设置线程本地副本变
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap的键值为set方法设置的ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
remove方法,移除线程本地副本变量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
让每个Thread对象,自身持有一个Map,这个Map的Key就是当前ThreadLocal对象,Value是本地线程变量值。相对于加锁的实现方式,这样做可以提升性能,其实是一种以时间换空间的思路
ThreadLocalMap
threadLocals是一种ThreadLocal.ThreadLocalMap 类型的数据结构,作为内部类定义在ThreadLocal类中,其内部采用一种WeakReference的方式保存键值对。
成员变量
private static final int INITIAL_CAPACITY 16:
private Entry[] table;
private int size =0
private int threshold;
跟 Hashmaps类似, INITIAL CAPACITY代表这个Map的初始容量; table是一个 Entry类型的数组,用于存储数据;Slze代表表中的存储数目; threshold代表需要容时对应size的阈值
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在 threadLocalmap中,也是用entry来保存K-V结构数据的。不过 Entry中的key只能是 Threadlocal对象,这点在构造方法中已经限定死了 另外, Entry继承 Weakreference,也就是key( Threadlocal)是弱引用,其目的是将 Threadlocal对象的生命周期和线程生命周期解绑
Thread threadLocal ThreadLocalMap三者关系:
- 每一个thread都有threadLocals其类型为threadLocal.ThreadLocalMap,threadLocal是线程Thread中属性threadLocals的管理者。
- threadLocalMap对象存放于thread对象中,其名称叫threadLocals是一个数组,数据是Entry类型,维护者一个或者多个Entry,Entry的key是ThreadLocal实例的弱引用,value是object类型,就是线程专属变量
- 通过threadLocal访问副本数据时,实际上时通过thread来获取theadLocalMap,在通过threadLocalMap的key来获取副本的数据
- threadLocalMap的key是threadLocal对象的弱引用,其目的是为了更好的对threadLocal进行回收。如果key被设置为强引用则该threadLocal则不能被回收
ThreadLocal与Synchronized
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,
不同的点是Synchronized是通过线程等待,牺牲时间来解决访问冲突只提供了一份变量让不同的线程排队访问,来保证多个线程之间访问资源的同步
ThreadLocal采用以空间换时间的方式为每一个线程都提供了一份变量的副本从而实现同时访问而相不干抗 并且相比于Synchronized,ThreadLocal具有线程隔离的效果,多线程中让每个线程之间的数据相互隔离,可以让线程之间并发执行
哈希冲突解决
构造函数首先创建一个长度为16的Enty数组,然后计算出 firstKey对应的索引,然后存储到table中并设置size和阈值
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
计算索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
firstKey.threadLocalHashCode
private final int threadlocalhashcode -nexthashcodeo
private static int nexthashcodeo
return pexthashcode. getandadd(HASH_INCREMENT);
private static Atomicinteger nexthashcode = new Atomicinteger O:
private static final int HASH_INCREMENT =0x61c88647
这里定义了一个 Atomiclntegers类型,每次获取当前值井加上 HASH INCREMENT= 0x61c88647这个值跟要波那列(金分割数)有关,其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里也就是 Entry table中,这样做可以尽量避免hash冲突
& (INITIAL_CAPACITY - 1)
计算hash的时候里面采用了 hash Code&(size-1)的算法,这相当于取模运算 hashcode%size的个更高效的实现,正是因为这种算法,我们求size必须是2的整次幕,这也能保证保证在素引不越界的前提下,使得hash发生冲突的次数减小
set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
A 首先还是根据key计算出索引1,然后查找位上的 Entry B. 若是 Entry已经存在并且key等于传入的key,那么这时候直接给这个 Entry赋新的value值 C.若是 Entry存在,但是key为nul,则调用 replacestaleentry来更换这个key为空的 Entry D.不断循环检查,直到遇到为nul的地方,这时候要是还没在循环过程中 return,那么就在这个nul的位新建一 个 Entry,井且插入,同时slze增加1 最后调用 cleansomeslots,清理key为null的 Entry,最后返回是否清理了 Entry,接下来再判断size是否>=阈值达到了 rehash的条件,达到的话就会调用 rehash函数执行一次全表的扫描清理
弱引用与内存泄漏
基本引用
Java中的引用有4种类型:强、软、弱、虚
强引用(StrongReference)
我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还活着,垃圾回收器就不会回收这种对象
弱引用( Weakreference)
垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
软引用(SoftReference)
是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等
虚引用(PhantomReference)
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
内存泄漏
- Memory overflow 内存溢出,没有足够的内存提供申请者使用
- Memory leak:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存
的浪费,导致程序运行速度减儒甚至系统期濡等严重后果工内存泄漏的堆积终将导数内存溢出
如果key使用强引用 key使用的是弱引用
ThreadLocal时会发生内存泄漏的前提条件:
- ThreadLocal引用被设置为null,且后面没有set,get,remove操作。
- 线程一直运行,不停止。(线程池)
- 触发了垃圾回收。(Minor GC或Full GC)
ThreadLocal是一个弱引用,但value 是强引用,当key为null时,ThreadLocal会被当成垃圾回收;但是此时ThreadLocalMap生命周期和Thread的一样,只有当前Thread结束之后,所有与当前线程有关的资源才会被GC回收。除非手动删除,否则它不会回收,这时候就出现一条强引用链Threadref–>Thread–>ThreadLocalMap–>Entry(key,value),value不会被回收,而这块 value永远不会被访向到了,导致valuel内存泄漏。
既然key是强引用还是弱引用都会内存泄漏,那为啥还要使用弱引用呢?
事实上,在 Threadlocalmap中的 set/getentry方法中,会对key为null(也即是 Threadlocal为null)进行判断,
如果为null的话,那么是会对 value置为null 这就意味着使用完 Threadlocal,线程依然运行的前提下,就算忘记调用 remove方法,弱引用比强引用可以多一层保障:弱引用的 Threadlocal会被回收,对应的 value在下一次 Threadlocalmap调用set. get, remove中的任一方法的时候会被清除,从而避免内存泄漏
但这仅仅是多一层保障,若永远解决内存泄漏请使用完调用remove方法
根本原因
由于ThreadLocalMapl的生命周期跟Thread样长,只要线程一直运行,如果没有手动删除对应key,因为value是强引用(即使key是弱引用)导致entry 的value无法被回收,所以强引用链Threadref–>Thread–>ThreadLocalMap–>Entry(key,value)一直存在,进而导致value的内存泄漏
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况
使用ThreadLocal时遵守以下两个小原则
- ThreadLocal申明为private static final。Private与final 尽可能不让他人修改变更引用,
- Static 表示为类属性,只有在程序结束才会被回收。ThreadLocal使用后务必调用remove方法。最简单有效的方法是使用后将其移除。
InheritableThreadLocal
ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能
public class Test {
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
public static void main(String args[]) {
threadLocal.set(new Integer(456));
Thread thread = new MyThread();
thread.start();
System.out.println("main = " + threadLocal.get());
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread = " + threadLocal.get());
}
}
}
InheritableThreadLocal类重写了ThreadLocal的3个函数:
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
子线程访问父线程的本地变量的根本原因:在构造Thread对象的时候对父线程的InheritableThreadLocal进行了复制
public class Thread implements Runnable {
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
}
只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。
InheritableThreadLocal之所以能够完成线程间变量的传递,是在new Thread()的时候对inheritableThreadLocals对像里的值进行了复制。
子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。
|