线程本地对象,为每个线程提供一个独立的变量,和其他线程是隔离的,互不干涉的。
ThreadLocal的简单使用:
public class ThreadLocalDemo {
//创建一个ThreadLocal对象,用来为每个线程会复制保存一份变量,实现线程封闭
private static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
//给变量设为10
localNum.set(10);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get()+10);//给变量+10
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//20
}
}.start();
new Thread(){
@Override
public void run() {
localNum.set(-10);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get()-10);
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//-20
}
}.start();
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//0
}
}
main线程没有对ThreadLocal变量操作,变量还是默认的0
而其他两个线程都对ThreadLocal变量进行了修改
但是修改只是线程自身使用的变量发生了变化,没有影响其他线程
ThreadLocalMap
Entry(ThreadLocal<?> k, Object v)
static class Entry extends WeakReference<ThreadLocal<?>>
threadLocals
属性,也就是ThreadLocalMap对象ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap的结构
/**
将此线程局部变量的当前线程副本设置为指定值。
大多数子类不需要重写此方法,仅依靠initialValue方法来设置线程局部变量的值。
参数:value-要存储在此线程本地的当前线程副本中的值。
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的属性 ThreadLocalMap 对象
//实现:return t.threadLocals; threadLocals就是ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
//获取的map不为空,直接更新键值对;获取的map为空,创建map,再赋值
if (map != null)
//this是ThreadLocal对象,使用ThreadLocal对象作为key
map.set(this, value);
else//第一次添加数据
//createMap实现:t.threadLocals = new ThreadLocalMap(this, firstValue);
createMap(t, value);
}
创建map的方法:
//创建一个ThreadLocalMap对象,赋给当前线程的map属性
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//初始化一个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);
}
/**
返回此线程局部变量的当前线程副本中的值。
如果变量没有当前线程的值,则首先将其初始化为调用initialValue方法返回的值。
@return:此线程本地的当前线程的值
*/
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程的map对象
if (map != null) {//拿到的map不为空,获取map中存的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//拿到的map为空,设置value的初始值为null,返回null
return setInitialValue();
}
setInitialValue()设置初始值
private T setInitialValue() {
T value = initialValue();//return null;
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void remove() {
//获得当前线程的map对象
ThreadLocalMap m = getMap(Thread.currentThread());
//map不为空,就移除该键值对
if (m != null)
m.remove(this);
}
首先ThreadLocalMap的Entry对象使用ThreadLocal的弱引用作为key
static class Entry extends WeakReference<ThreadLocal<?>
如果线程不结束:存在这样一条强引用链
CurrentThread 引用 -> CurrentThread 对象 -> ThreaLocalMap 对象 -> Entry 对象-> key(ThreadLocal对象)
每次使用完ThreadLocal为线程提供的变量后,调用remove()方法,删除该键值对对象。
存储用户session
数据库连接、处理事务
Spring使用ThreadLocal解决线程安全问题
Spring对一些Bean中一些非线程安全的对象采用ThreadLocal进行封装(如RequestContextHolder
、TransactionSynchronizationManager
、LocaleContextHolder
…),让它们也成为线程安全的对象
如此有状态的Bean就能够以singleton的方式在多线程中正常工作了
一个线程只使用一个ThreadLocalMap的话,使用Thread作为key,确实可以
public class Test{
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
}
但是就算在单线程中,一个类的变量往往也不是只有一个,可能有多个
在一个多线程的应用程序中,可能有多个不同的共享资源,多个线程去访问它们
public class Test{
private static final ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
private static final ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
private static final ThreadLocal<Integer> threadLocal3 = new ThreadLocal<>();
}
如果采用Thread做为ThreadLocalMap中Entry的key,那么当前线程如何准确地知道要获取哪一个ThreadLocal对象?
所以必须用 ThreadLocal 作为key,才能使用具体ThreadLocal对象的get方法准确获取数据
使用new创建的新对象,将引用指向一个变量,该变量是指向新对象的一个强引用
Object obj = new Object( );
只要强引用对象是可触及的->就不会被GC回收掉
只要强引用对象是可达的 -> 就不会回收;
强引用是造成内存泄漏的原因之一。
使用Soft Reference来管理的对象属于软引用
被软引用关联的对象只有在内存不够的时候才会被回收
Object obj = new Object();// 声明强引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用
使用场景:
高速缓存中使用软引用,还有空闲内存就暂时保留缓存;内存不够时再清理垃圾;
保证了使用缓存的同时,不会耗尽内存。
使用Weak Reference管理的对象,只能存活到下次垃圾回收之前
垃圾回收时,不管内存够不够,一律回收弱引用的对象
Object obj = new Object(); // 声明强引用
WeakReference<Object> sf = new WeakReference<>(obj);
obj = null; //销毁强引用
也叫“幽灵引用”;使用Phantom Reference管理的对象
需要维护一个引用队列,创建虚引用对象时需要提供一个队列作为参数
Object obj = new Object(); // 声明强引用
ReferenceQueue phantomQueue = new ReferenceQueue(); // 声明引用队列
// 声明虚引用(还需要传入引用队列)
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;
如果设计为强引用,如图
ThreadLocal如果生命周期结束,置为null后,由于key对ThreadLocal是强引用,
存在一条强引用链:
Thread -> ThreadLocalMap -> Entry[] -> Enrty -> key->ThreadLocal对象
ThreadLocal和ThreadLocalMap并不会被回收掉,产生了内存泄漏问题
因此设计为弱引用,在GC下一次清理垃圾时,就会被自动回收掉
1.WeakReference构造器中直接传入new的对象,没有其他引用
public static void main(String[] args) {
WeakReference<Object> weakReference0 = new WeakReference<>(new Object());
System.out.println(weakReference0.get());
System.gc();
System.out.println(weakReference0.get());
}
结果: GC后弱引用会被直接回收
java.lang.Object@5cad8086
null
2.定义一个强引用的对象,将对象传入WeakReference构造器
public class TestWeakRef {
public static void main(String[] args) {
//强引用指向对象
Object object = new Object();
//传入强引用
WeakReference<Object> weakReference = new WeakReference<>(object);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
//object引用->Object对象->weakReference对象
}
}
结果: 没有被回收:存在强引用关系的弱引用不会被回收
java.lang.Object@5cad8086
java.lang.Object@5cad8086
3.断开强引用对弱引用关联
public class TestWeakRef {
public static void main(String[] args) {
Object object = new Object();
WeakReference<Object> weakReference1 = new WeakReference<>(object);
System.out.println(weakReference1.get());
System.gc();
System.out.println(weakReference1.get());
object=null;
System.gc();
System.out.println(weakReference1.get());
}
}
结果: 强引用=null,弱引用可以被回收掉
java.lang.Object@5cad8086
java.lang.Object@5cad8086
null
总结: 强引用与弱引用同时关联一个对象,这个对象不会被GC回收掉,ThreadLocalMap.Entry的key也是一样,只有强引用断开连接,弱引用的对象才能被回收