ThreadLocal为每隔使用该变量的线程提供独立的变量副本。
并发环境下 我们使用simpledataformat。我们可以封装一下用threadlocal来保存。
为什么这么做呢?
- simpledateformat是不安全的
- simpledateformate中的calendar线程是不安全的
- calendar是线程不安全,为什么不安全?
- 类中存放日期数据变量是线程不安全的,比如里面的time等属性
public class TestSimpleDateFormat2 {
// (1)创建threadlocal实例
static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args) {
// (2)创建多个线程,并启动
for (int i = 0; i < 10; ++i) {
Thread thread = new Thread(() -> {
// (3)使用单例日期实例解析文本
try {
System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
} catch (ParseException e) {
e.printStackTrace();
}
});
thread.start();// (4)启动线程
}
}
}
ThreadLocal源码分析
public void set(T value) {
Thread t = Thread.currentThread();
//1
ThreadLocalMap map = getMap(t);
if (map != null)
//key ThreadLocal本身,value:线程的变量副本(value)
map.set(this, value);
else
createMap(t, value);
}
//1.1
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//1.2 thread中引用的threadLocalmap变量
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
//没有则创建map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal只是在set的时候去操作了thread内部的ThreadLocalMap
Key就是当前的ThreadLocal,Value就是变量的副本
get方法:
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();
}
如何创建ThreadLocalMap
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);
}
1.创建的时候 会生成一个entry数组
2.通过与运算获取table下标数组i
3. 每个table对应一个entry数组,也就是 数组+entry(key,value)结构
看一下如何创建entry?
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
什么是强引用?等号(=)关系,我们认为是强引用。
什么是弱引用?使用WeakReference
上面的entry就是 使用弱引用。
但是 请注意
继承 WeakReference,将ThreadLocal<?> k,作为弱引用,回收的是k,不是entry,不是value。
如果回收的是k,那么变为null,value还是强引用。
顺便提一下如何找到回收的对象?(那些对象能回收,哪些对象不能回收)
- 引用计数法:jvm不会用了。
- 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
哪些对象可以被看做是 GCRoots 呢?
什么是GCROOTS:从根节点向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中的类静态属性引用的对象,常量引用的对象;
- 本地方法栈中 JNI(Native 方法)引用的对象;
梳理一下ThreadLocal到底长什么样子?
threadlocal内部有一个静态类, threadLocalmap,用来存储键值对
每次set线程的时候,先获取一下当前线程,然后看有没有localmap
如果没有,则创建map
如果有,则将 threadLocal作为key,值作为value保存在 treadLocalMap的一个entry数组中。
并且这个entry数组是使用弱引用的,需要注意的是,弱引用仅仅是引用的key,那么就会出现一个问题,如果回收了key,key变为了null,那么value就会在内存中不会被回收。
重新思考为什么这么做
为什么thread持有ThreadLocal中的threadLocalMap?
从几方面来考虑:
- threadLocal仅仅是作为一个工具类,那么在设计的时候就不应该持有线程的任何数据,线程的任何数据在thread里面。
为什么entry 的key为弱引用?
假设下面一段代码:
ThreadLocal<M> tl = new TheadLocal<>(); //tl强引用threadlocal
tl.set(new M());//
tl.remove()
Thread线程 长期存在,tl长期存在,map就会长期存在,那么key也就会长期存在。
当线程持有的threadLocal 释放的时候,threadLocal由于持有threadLocalMap的弱引用,threadLocalMap即使没有手动释放,都会回收它。
但是需要 注意的是key 为null的时候,导致value无法回收内存。
所以threadLocalMap调用 set(),get(),remove()方法的时候会判断并且清空value的值。
我们在写代码的时候,需要添加finnally代码块,并且调用remove()方法,来清除key为null的情况。