ThreadLocal
在使用类的静态变量作为ThreadLocal的值时,会出现线程不安全
简介
ThreadLocal 是一个关于线程局部变量的类,通常情况下我们创建的变量是可以被任意线程访问并修改的,而 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问和修改。
能实现线程独享的变量是因为每个 Thread 内部都有一个默认为 null 的 ThreadLocalMap,保存或获取的时候都是通过当前线程获取到自己的 ThreadLocalMap 进行操作的,而 ThreadLocalMap 内部是一个 Entry,Entry 的 key 是弱引用。
使用
public class ThreadLocal<T> {
protected T initialValue() //初始化方法,protected方法,没有 set 值时,默认返回 Null,用于子类重写;
public void set(T value) //设置值;
public T get() //获取值;
//清空值。当线程回收时,局部变量也会自动回收
// 虽然理论上主动调起是非必须操作,只是加快回收速度,但是为了避免内存泄露每次用完需要 remove。
public void remove()
}
set 方法解析
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程里的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 直接存储,key 是 this,所以说明一个 ThreadLocal 只能存一个独享值
map.set(this, value);
else
// 创建 map,懒加载形式
createMap(t, value);
}
使用场景
Spring 的事务上就使用了 ThreadLocal,我们一般把 @Transactional 注解写在 service 层上,而一个service 方法里面可能会调用多个 dao 层的方法,为了让每个线程保持自己的数据库连接,就使用了ThreadLocal,在整个事务过程都使用与该线程绑定的 Connection 来执行数据库操作,实现了事务的隔离性。
四种引用
- 强引用
- 软引用
- 弱引用
- 虚引用
强引用
如果一个对象具有强引用,就不会被回收,即使内存不住,也只会抛出 OOM 异常,一般 a = new XXX 的时候都是强引用,想让被回收,只有把 a = null 在下次 GC 的时候就会被回收了。
软引用
在内存够用的时候不会被回收,内存不够用的时候会自动把软引用回收掉,主要用于缓存。
SoftReference<String> softName = new SoftReference<>("软引用");
弱引用
当 gc 线程发现某个对象只有弱引用指向它,那么就会将其销毁并回收内存,不管内存是否充足。
// 会被回收
WeakReference<String> weakName = new WeakReference<String>(new String("hello"));
// 不会被回收,因为 new String("hello") 这块区域不仅被 WeakReference 引用,还被 hello 引用
// String hello = new String("hello");
// WeakReference<String> weakName = new WeakReference<String>(hello);
System.out.println(weakName.get());
System.gc();
System.out.println(weakName.get());
使用案例:ThreadLocal.ThreadLocalMap.Entry
ThreadLocal 内存泄露
上面说到 ThreadLocal 中 ThreadLocalMap 中的 Entry 的 key 是弱引用,那么以下列出 Entry 是强软弱引用时各自发生内存泄露的情况:
如上上述,虽然 Entry 使用的是弱引用,但是内部还有一个 value 是强引用,所以 ThreadLocal使用完毕之后应该调用 remove() 方法。
虚引用
虚引用的主要作用是跟踪对象垃圾回收的状态,必须和ReferenceQueue联合使用。仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。
PhantomReference 的 get 方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。
public static void main(String[] args) {
Reference<A> pr = new PhantomReference<A>(new A(), REFERENCE_QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(pr.get());
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends A> poll = REFERENCE_QUEUE.poll();
// 队列里有值了说明 A 对象被回收了,可以做一些想做的操作
if (poll != null) {
System.out.println(poll.get() + "虚引用被回收了");
}
}
}).start();
}
private static final List<Object> LIST = new LinkedList<>();
// 在 A 对象被回收了之后的某个时机会被加入到这个队列里
private static final ReferenceQueue<A> REFERENCE_QUEUE = new ReferenceQueue<>();
static class A{
int a = 0;
@Override
protected void finalize() throws Throwable {
System.out.println("回收了");
}
}