敌不动,我不动;敌若动,我就专门去公司上班,唱一整天歌和我同桌听,我很任性的~
ThreadLocal介绍
"This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID)."
乱乱翻:这个类提供了线程本地变量(在该线程生命周期内起作用)。每个线程都有自己独立的变量备份。在类中,ThreadLocal实例通常被修饰为private static,用于关联线程和状态(举例,user ID或者transaction ID)
ThreadLocal应用场景
减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度(获取变量,释放变量)。
ThreadLocal源码解析
构建函数:
public ThreadLocal() {
}
内部静态类ThreadLocalMap:
用来存储ThreadLocal实例与变量的映射,key是弱引用,当它为null的时候,对应的entry会被当做过期的条目,从表中被移除。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
每个线程都有一个ThreadLocalMap属性,
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Getter:
- 第一次调用get函数的时候会调用变量初始化函数initialValue()
- 如果一开始调用了set函数,initialValue()就不会被调用
- 如果手动调用了remove函数之后又调用了get函数,此时仍然会调用initialValue()
protected T initialValue() {
return null;
}
我们可以重写该函数,通常该函数都会在匿名内部类里面被重载,以指定初始值:
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
@override
protected Integer initalValue() {
return Integer.valueOf(1);
}
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
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);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1. 获取当前线程的threadlocals,如果map为null,则创建ThreadLocalMap实例,参数为当前ThreadLocal实例和对应的本地变量
2. 若map不为空,但没有存放当前ThreadLocal实例对应的变量时,就set
T value = initialValue();
内存泄漏:
ThreadLocalMap采用弱引用当key,可能会造成内存泄漏:
因为如果ThreadLocal对象被GC之后,key就会变成null,此时就没法访问该entry,如果该线程迟迟没有结束,ThreadRef -> Thread -> ThreadLocalMap -> entry -> value(引用一直存在),该entry将一直没办法回收,会造成内存泄漏。可以将ThreadLocal变量定义成private static,防止外部将它设为null,并且在调用getEntry和set方法时删除过时的entries。
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- table默认capacity是16
- key哈希值与len-1取模得到index
step 1. 根据index,得到对应的entry,当entry为null时,跳到step 5
step 2. 判断它的key是否等于指定key,是的话,直接替换值,返回
step 3. 如果该entry过时,查找table中对应指定key的entry,存在则替换值,并将过时entry与该entry替换;不存在则直接替换过时entry的key和value
step 4. 冲突,调用nextIndex得到新的index,转step 1
step 5. table[index] = new Entry(key, value) ,size++
step 6. 清理table,如果size超过threshold(len*2/3),调用rehash方法(清理表中所有过期的entries,然后再size大于threshold*3/4的时候扩容)
Remover:
将key设置为null,然后调用expungeStaleEntry函数来清除过时entry
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
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.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
public void clear() {
this.referent = null;
}
例子
复习 && Kinds of references
GC roots:
- JVM栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
- Native Method Stack中JNI引用的对象
(静态变量,常量不会被回收)
方法区:各个线程共享的内存区域,存储已被虚拟机加载的类信息,变量,静态变量等数据
1. 调用System.gc()是告诉JVM尽快GC一次,但不会立即执行
2. JVM中两个互相引用的对象如何被GC?
Mark-and-Sweep Algorithm
1. 从GC roots遍历所有的object references,将发现的每个对象mark成alive
2. 在堆中没有被标记的对象会被reclaimed(简单的标记成free,不去用)
GC Finalize:
1. finalize()是Object的protected方法,方法内没有任何操作,我们可以重写这个方法来实现JNI内对象的清除,并且可以用它来释放某些非内存资源如socket,文件等
2. 当对象变成不可达时,GC会判断该对象是否重写了finalize方法,如果没有覆盖,则将其回收,否则将finalize方法放进F-Queue队列中。当finalize方法被执行完毕,GC会再次判断对象是否可达,不可达就回收
3. 不能保证finalize()具体执行的时间,JVM通常在单独的低优先级线程中完成该方法的执行。一个finalize方法最多只能由GC执行一次,对象可以在finalize方法中复生
对象的五种状态:unfinalized, finalizable, finalized, reachable(表示GC roots引用可达), finalizer-reachable(表示不是reachable,但是可以通过某个finalizable对象可达), unreachable
- StrongReference
它是JAVA的默认引用实现,对象会尽可能长时间的存活于JVM内,当没有任何引用指向某个对象的时候,该对象会被GC回收
- WeakReference & WeakHashMap
当对象在JVM内不再有强引用时,则该对象在GC后会被自动回收。而WeakHashMap使用WeakReference对象作为key,一旦没有指向该key对象的强引用,WeakHashMap在GC后将自动删除相关的entry。当key被回收时,虚拟机会自动将这个entry插入到ReferenceQueue中,WeakHashMap就是利用ReferenceQueue来清除key为null的entries
- SoftReference
与WeakReference的特性基本一致,最大的区别就是SoftReference会尽可能长的保留引用直到JVM内存不足的时候才会被回收
- PhantomReference
PhantomReference与WeakReference和SoftReference有很大的不同,因为它的get()方法永远返回null
- PhantomReference VS WeakReference
PhantomReference可以跟踪referent何时被enqueue到ReferenceQueue中(Object的finalize方法在GC执行前被调用,如果某个对象重载了finalize方法并故意在方法内创建本身的强引用(复生),将导致GC无法回收这个对象,而PhantomReference就可以避免这个)
class C {
static A a;
}
class A {
B b;
public A(B b) {
this.b = b;
}
@Override
public void finalize() {
System.out.println("A finalize");
C.a = this;
}
}
class B {
String name;
int age;
public B(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void finalize() {
System.out.println("B finalize");
}
@Override
public String toString() {
return name + " is " + age;
}
}
public class Main {
public static void main(String[] args) throws Exception {
A a = new A(new B("allen", 20));
a = null;
System.gc();
Thread.sleep(5000);
System.out.println(C.a.b);
}
}
Reference
1. http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
2. http://blog.youkuaiyun.com/mxbhxx/article/details/9111711
3. http://blog.sina.com.cn/s/blog_4a4f9fb50100u908.html
4. http://stackoverflow.com/questions/1910194/how-does-java-garbage-collection-work-with-circular-references
5. http://blog.youkuaiyun.com/pi9nc/article/details/12374049
6. http://zhang-xzhi-xjtu.iteye.com/blog/484934