一、ThreadLocal
每个线程拥有一个独有的ThreadLocalMap对象
,线程需要自己往里面设值,之后才能读到自己设置的变量值,线程对他们各自拥有的ThreadLocal对象做更改操作时,其他线程是获取不到这些改动的;
结合下图分析:
线程中set ThreadLocal对象Object o时:是在当前线程的一个map中加入一个K-V,K是一个ThreadLocal,V是我们往这个ThreadLocal容器中加入的一个对象。
(也就是说map中一个entry对应一个TreadLocal对象(Key)以及一个ThreadLocal对象容器中存放的对象(Value))
set:Thread.currentThread.map(ThreadLocal, Object o)
每一个线程有一个
ThreadLocalMap(WeakHashMap)
,里面的每一个entry中的K是一个ThreadLocal容器,V是一个容器中的对象,每一个线程的一个栈帧指向内存中他独有的一个ThreadLocal,对应该线程map中的一个entry;
当GC时,由于key对ThreadLocal是弱引用,threadLocal对象会被回收,key会指向null,而该entry中的value是强引用,也就是threadLocal中的对象不会被回收,导致该entry始终不会被回收,只有当线程结束时才会回收整个threadLocalMap,
所以需要手动调用tl.remove()释放tl这个threadLocal所在的entry;
ThreadLocal应用:
Spring的声明式事务中,在一个事务中为了保证多个方法获取到的是同一个与数据库之间的连接,第一个方法从数据库连接池中拿到一个连接,将该连接对象放到一个threadLocal tl中tl.set(连接1),之后该事务中的其他方法都从该tl中get这个连接tl.get() — 连接1,
如此就可以保证一个事务内部都调用同一个连接对象。
- 简单的使用ThreadLocal的小程序如下
public class ThreadLocalDemo {
static ThreadLocal<Person> tl = new ThreadLocal<>();
static ThreadLocal<Person> tl2 = new ThreadLocal<>();
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
new Thread(()->{
try {
tl.set(new Person());
tl2.set(new Person());
tl2.get().setName("wangwu");
TimeUnit.SECONDS.sleep(2);
/** 线程自己没有设置tl中的成员变量,输出为null */
System.out.println(Thread.currentThread().getName() + tl.get().getName() + tl2.get().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
/** 防止内存泄漏,手动释放tl所在的entry */
tl.remove();
tl2.remove();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
tl.set(new Person());
tl.get().setName("lisi");
/** 获取到自己设置的成员变量的值lisi */
System.out.println(Thread.currentThread().getName() + tl.get().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
/** 防止内存泄漏,手动释放tl所在的entry */
tl.remove();
}
}).start();
}
}
class Person{
int age;
String name;
String addr;
Person(){}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
补充知识
二、弱引用(WeakReference)
一般应用在容器中
(WeakHashMap),因为一旦发生gc就会被回收,其意义是该对象除了一个弱引用之外还有一个强引用,一旦该强引用被取消,该对象就应该自动被回收,不需要程序员去管理。
在以上ThreadLocal中的应用就是体现在线程栈中有一个局部变量tl强引用指向内存中的一块内存空间,也就是ThreadLocal的对象tl,线程栈中的一个map指向内存中该线程的一个map对象,其中一个entry对象的key弱引用指向同一个tl对象的内存空间(tl堆内存被一个强引用和一个弱引用指向);
当程序逻辑中要释放对tl对象的引用时强引用先断开,此时entry中的弱引用在下次gc时就会主动断开该弱引用的连接,gc回收该tl对象内存空间(
对应上面说的一旦强引用断开,容器中该对象就会自动被gc回收
)。但是此时entry中的value仍然有一个强引用指向一个对象,gc无法回收该entry内存空间(其实程序逻辑本意已经删除了tl,自然后续不需要再使用该tl中的对象了,也即value指向的对象,应该同时删除该无用的entry对象);
此时为了释放该无用的entry内存空间,需要手动调用remove方法,gc就会回收该entry。
三、虚引用(PhantomReference)
基本用不到,netty中的直接内存DirectByteBuffer用到
(针对堆外内存,即不被JVM管理的内存,该部分内存由操作系统直接管理)
PhantomReference<M> phantomReference = new PhantomReference<> (new M(), Queue)
程序运行时拿不到该虚引用指向的对象M,仅用来触发一个消息通知
。
当该虚引用指向的对象M被回收的时候,该对象会进入一个消息队列Queue中,此时如果有一个线程一直在监视该队列Queue中是否有对象加入,就会触发该线程监视到Queue中加入元素后的后续逻辑操作;
应用:虚引用指向一个堆外内存对象,该引用被回收时通知JVM调用Unsafe类的freeMemory方法释放其指向的堆外内存空间。(Unsafe也就是通过C来控制操作系统管理的内存)
简单的虚引用如下所示: