一、引用分类
在了解ThreadLocal之前需要先知道java中的引用方式。
分为四种:强软弱虚
- 强引用 Strong Reference
最普遍的引用,代码中的obj和str都是强引用
Object obj = new Object();
String str = "hello";
➢抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
➢通过将对象设置为null来弱化引用,使其被回收
- 软引用 Soft Reference
➢对象处在有用但非必须的状态
➢只有当内存空间不足时, GC会回收该引用的对象的内存
➢可以用来实现高速缓存(作用)
Object obj = new Object();
SoftReference<Object> sr =new SoftRefernce<Object>(obj);//软引用
其中,sr就是软引用
- 弱引用 Weak Reference
➢非必须的对象,比软引用更弱一-些
➢GC时会被回
➢被回收的概率也不大,因为GC线程优先级比较低
➢适用于引用偶尔被使用且不影响垃圾收集的对象
String str=new String( original: "abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
- 虚引用 PhantomReference
➢不会决定对象的生命周期
➢任何时候都可能被垃圾收集器回收
➢跟踪对象被垃圾收集器回收的活动,起哨兵作用
➢必须和引用队列ReferenceQueue联合使用
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
String str=new String( original: "abc");
ReferenctQueue queue = new ReferenceQueue();
PhantomReference ref = new PhantomReference(str, queue);
常用于对外内存管理,我们知道的nio 的directbytebuffer就是虚引用最常见的应用。
二、ThreadLocal
- 什么叫ThreadLocal
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
- ThreadLocal实现原理
首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。例如下面的 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);
}
get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap是个静态的内部类:
static class ThreadLocalMap {
.....
}
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
由上面分析可以得知,ThreadLocal 就是Thread中的Map下存的key。
- ThreadLocal为什么使用弱引用
查看源码发现ThreadLocalMap 中有一个Entry对象,而这个Entry对象继承于WeakReference
话不多说上源码:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看出,Entry中的key有一个弱引用指向ThreadLocal对象。在内存中就是下面这个图所示的情况:
在这里我们要思考,为什么要使用弱引用呢?
答案是:防止内存泄漏。
当ThreadLocal 所指向的对象引用消除时,弱引用所指向的对象会在被GC时所回收,不会存在内存泄露问题。
这里注意一个问题:当ThreadLocal对象不用时,要调用t.remove()。
- ThreadLocal 实际的应用场景
我们知道spring中的@Transaction 是用于保证该方法保证数据一致性和完整性。
我们对数据库连接操作,如何保证同一个方法中,不同的数据库交互使用的是同一个Connection,那么就要使用ThreadLocal,保证该方法中,无论多少次的数据库交互都使用同一个连接。