ThreadLocal
1.ThreadLocal简介
通常情况下,我们的变量可以被任何一个线程访问并修改。如果想每个线程都有一个自己的专属本地变量该怎么办呢?ThreadLocal解决了这个问题。
ThreadLocal类主要解决的就是让每一个线程绑定自己的值,ThreadLocal可以看作是一个放数据的盒子,这个盒子可以存放线程的私有数据。当我们在多线程下使用SimpleDateFormat类的时候可能出现过线程安全问题(这里不做详细介绍),可以使用ThreadLocal来解决这个问题。
2.ThreadLocal示例
public class ThreadLocalExample implements Runnable { //SimpleDateFormat 不是线程安全的,所以每个线程都要有⾃⼰独⽴的副本
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() i> new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name="+Thread.currentThread().getName()+" default Formatter ="+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won'treflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter "+formatter.get().toPattern());
}
}
运行结果
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
3.ThreadLocal原理
首先看看Thread的一个源码
public class Thread implements Runnable{
......
//与该线程有关的ThreadLocal值
ThreadLocal.ThreadLocalMap threadLocals = null;
//与该线程有关的InheritableThreadLocal值
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
从这个源码可以看出来Thread类有两个变量:threadLocals和inheritableThreadLocals,这两个变量都是ThreadLocalMap类型的。默认这两个变量是null,只有当前线程调用ThreadLocal类的get或set方法的时候才会创建他们。调用get、set方法实际上是调用了ThreadLocalMap的get()或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;
}
通过上面的分析可以得出:变量存放在了ThradLocalMap中,并不在ThreadLocal上,ThreadLoca可以看作是ThreadLocalMap的也给封装类型。先通过Thread.currentThread()方法得到当前线程对象,然后调用ThreadLocalMap的getMap(Thread t)方法得到该线程的ThreadLocalMap对象
4.ThreadLocal内存泄露问题
ThreadLocalMap中使用的key是ThreadLocal的一个弱引用,而value是一个强引用。所以当ThradLocal没有被外部强引用的话,在垃圾回收的时候会被清理掉,而value不会被清理掉。这时会出现key为null的Entry。如果我们不做任何措施的话就会造成内存泄露问题。解决方法:ThreadLocalMap考虑了这种情况,在调用get(),set(),remove()方法时都会清理掉key为null的记录。在使用完ThreadLocal方法后最好手动调用remove()方法。
,
,
,
弱引用介绍
如果⼀个对象只具有弱引⽤,那就类似于可有可⽆的⽣活⽤品。弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。不过,由于垃圾回收器是⼀个优先级很低的线程, 因此不⼀定会很快发现那些只具有弱引⽤的对象。弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃圾回收,Java虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。