什么是ThreadLocal变量
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
四种引用
软引用
SoftRerence <byte[]> m = new Reference<>(new byte[1024*1024]);
当内存不够时,软引用的对象会回收。
缓存中用的比较多。
弱引用WeakReference(ThreadLocal)
只要垃圾回收就回收
虚引用(管理堆外内存)
PhantomReference<M> phantomReference = new PhantomReferecne<>(new M(),QUEUE);
作用:管理堆外内存
原理
在ThreadLocal内有
ThreadLocalMap·
内部类,一切操作都是围绕这个内部类展开的
ThreeadLocalMap
的Key为虚引用
,key为threadLocal
自身
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。
在Thread这个类中维持了一个ThreadLocalMap对象,ThreadLocal.ThreadLocalMap threadLocals = null;
,在这个对象中就可以存储对象,每个Thread线程
都有自己的一个ThreadLocalMap
,然后 这个ThreadLocalMap的key就是每个
ThreadLocal
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
* 如果我们使用线程池,那就意味着当前线程未必会退出
* (比如固定大小的线程池,线程总是存在)。如果这样,将一些大大的对象设置到
* ThreadLocal中(它实际保存在线程持有的ThreradLocalMap中),可能会使得系统出现内存泄露的问题
*
*/
public void set(T value) {
//获取当前想线程
Thread t = Thread.currentThread();
//拿到当前线程的ThreadLocalMap
//这个值保存了当前自己所在线程的所有“局部变量”
//每个线程各自的ThreadLocalMap
//ThreadLocalMap可以理解为Map,它是定义在Thread内部的成员
// 设置到ThreadLocal中的数据,也正是写入了threadLocals这个Map
//其中key是ThreadLocal当前对象,value就是我们需要的值
//threadLocalas本身就是保存了当前自己所在线程的所有局部变量。
ThreadLocalMap map = getMap(t);
//每个线程都有一个ThreadLocalMap
// 每个线程存在ThreadLocalMap,在这个线程的threadLocalMap中可能存在多个threadLocal(线程的局部变量)
// threadLocal --------------> 值 (局部变量)
// threadLoca2 -------------->值
if (map != null)
map.set(this, value);//key为当前ThreadLocal对象,value使我们需要的值
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象,
//通过key
ThreadLocalMap map = getMap(t);
if (map != null) {
//将自己作为key取得内部的实际数据
//ThreadLocal作为key
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
内存泄露问题
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values.
*
* No operations are exported outside of the ThreadLocal class.
*
* The class is package private to allow declaration of fields in class Thread.
*
* To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys.
*为了应对大对象的生存,这里使用了若引用。
*
* However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
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).
*这个哈希映射中的条目扩展了WeakReference,
* 使用它的主ref字段作为键(它总是aThreadLocal对象)。
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.
* 注意,空键(即entry.get())表示键不再被引用,因此
可以从表中删除条目。
ThreadLocalMap的实现使用了弱引用 ,若引用是比强引用弱的多的引用,
Java虚拟机在垃圾回收时,如果发现弱引用就会立即回收。
如果对于ThreadLocal的变量,我们也手动将其设置为null,比如t1=null
那么这个ThreadLocal所对应的局部变量是有可能被回收的(这里是因为需饮用)
ThreadLocalMap内部由一系列Entry构成,每一个Entry都是WeakReference<ThreadLocal<?>>
*/
//出于内存泄露的考虑将其设置为若引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
/**
*
* 虽然这里使用ThreadLocal作为 Map的key,但是实际上,
* 它并不真的持有ThreadLocal的引用。而当ThreadLocal的外部引用被回收时,ThreadLocal中的key就会变成null
* @param k
* @param v
*/
Entry(ThreadLocal<?> k, Object v) {
//k是ThreadLocal实例,作为若引用(super(k)就是调用了WeakReference的构造函数)
//当ThreadLocal的外部强引用被回收时,ThreadLocalMap中的key就会变成null
//当系统进行ThreadLocalMap清理时
// (比如将新的变量加入表中,就会自动进行一次清理)
super(k);//key为若引用,key是Map的key
//
value = v;//v是
}
}
使用场景
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
存储用户session
存储数据库连接