1.什么是ThreadLocal?
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程。
该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
适用场景:
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
2.关键Class类
ThreadLocal,Thread,ThreadLocalMap 三个关系如下:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
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();
}
}
Thread里面有一个ThreadLocalMap类的属性,ThreadLocalMap是一个对象数组,对象是个软引用
3.ThreadLocal 使用Demo
public class ThreadLocalDemo {
private static ThreadLocal<String> threadLocal;
public static void main(String[] args) {
threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "初始化值";
}
};
for (int i = 0; i < 10; i++){
new Thread(new MyRunnable(), "线程"+i).start();
}
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "的threadLocal"+ ",设置为" + name);
threadLocal.set(name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(name + ":" + threadLocal.get());
}
}
}
执行结果如下:
4.ThreadLocal 原理解析
(1 )ThreadLocal的set方法
- 获取此时运行线程Thread.currentThread
- 获取线程的threadLocals属性(ThreadLocalMap 类型)
- 将值设置到当前线程的threadLocals里面进去。
public void set(T value) {
//获取此时运行线程
Thread t = Thread.currentThread();
//获取线程的threadLocals属性
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap 是ThreadLocal 的静态内部类
static class ThreadLocalMap {
//初始容量大小设置为2的n次方,是为了方便拿(len-1)跟(hashcode)进行与计算 => hashCode&1111
private static final int INITIAL_CAPACITY = 16;
//数组对象
private Entry[] table;
//容量
private int size = 0;
//阈值,超过阈值数组要进行扩容操作
private int threshold;
static class Entry extends WeakReference<ThreadLocal<?>> {
//每个线程独有变量的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocalMap的set 方法解析
- 根据key的hashcode和(len-1)进行与计算,得到数组下标
- 算出来的数组下标可能已经被其他占有了(hash冲突),解决hash冲突的方法是开放定址法,继续找下一个,直到找到一个为空的位置。
- 设置值 tab[i] = new Entry(key, value),并判断是否超过阀值,进行扩容rehash
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
//这个位置的key过期了,重新设置新的key 和value进去
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//并判断是否超过阀值,进行扩容rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
(2)ThreadLocalMap的get方法解析
- 从当前线程获取ThreadLocalMap类型的属性threadLocals
- 如果有值,则返回对应的值
- 没有的值,则设置map.set(this,null)
public T get() {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、从当前线程获取ThreadLocalMap类型的属性
// ThreadLocalMap getMap(Thread t) {
// return t.threadLocals;
// }
ThreadLocalMap map = getMap(t);
//3、如果map数据为空
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//4、如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();//null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
(3)ThreadLocalMap的remove方法解析
- 获取当前线程的ThreadLocalMap,将key为ThreadLocal移除出去。
- 顺便进行清除任务expungeStaleEntry(i),从当前位置开始,往后再找一段,碰到脏entry进行清理,碰到null结束
public void remove() {
//获取当前线程的ThreadLocalMap,将key为ThreadLocal移除出去。
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);
//因为之前hash冲突的时候,会找后一位空的位置。
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//找到对应的位置,然后进行移除。
if (e.get() == key) {
e.clear();
//从当前位置开始,往后再找一段,碰到脏entry进行清理,碰到null结束
expungeStaleEntry(i);
return;
}
}
}
4.ThreadLocal 内存泄露问题解析
内存泄漏: 是程序在申请内存后,这块内存后面不用了。因为存在引用,导致无法释放已申请的内存空间,多次内存泄漏就会导致内存耗光的严重问题。
强应用:
常用的引用都是强引用,如果一个对象具有强引用,GC绝不会回收它,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
String str = "abc";
List<String> list = new ArrayList<String>();
list.add(str);
思考题: 此时设置str = null,请问"abc"这个对象会被回收吗?(是不会被回收的,被list强引用着)
public static void main(String[] args) {
String str = "abc";
List<String> list = new ArrayList<String>();
list.add(str);
str =null;
System.out.println(list.get(0));
}
输出abc
弱引用(WeakReference):
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
软引用和弱引用都可以和一个引用队列(ReferenceQueue)联合使用,所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
A a = new A();
WeakReference<A> b = new WeakReference(a);
b.get();
变量a 是A对象的强引用。变量b 是 WeakReference对象的强引用,WeakReference对象的T属性是A对象的弱引用。
这样的话,当A对象没有强引用a的时候,只剩下WeakReference对象的T属性弱引用,会被垃圾回收器回收。但是WeakReference对象还有b强引用,是不会被回收的,这样b.get()得到的值为null。
WeakReference解析
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
Entry 对象有两个属性:WeakReference<ThreadLocal<?>> key 和 Object Value
ThreadLocalMap 里面的key是ThreadLocal 的软引用,当ThreadLocal 这个对象设置为null的时候,没有强引用,ThreadLocal 会被回收,但是ThreadLocalMap还持有value的强引用,如果没有手动删除,value就不会被回收,导致Entry内存泄漏。
只有等Thread线程退出,value的强引用才没有,value才会被回收。
所以,每次使用完ThreadLocal都要调用它的remove()清除数据,才不会出现内存泄漏
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//因为之前hash冲突的时候,会找后一位空的位置。
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//找到对应的位置,然后进行移除。
if (e.get() == key) {
e.clear();
//从当前位置开始,往后再找一段,碰到脏entry进行清理,碰到null结束
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 1. 将当前的脏entry 置为null,value 置为 null, size,即entry 的数量 减一
// 这里将value的值设置为null,断开强引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//依次循环的使index往后移,直到找到一个 entry = null ,则退出,并返回这个 index
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;
}