ThreadLocal 与四种引用

65 阅读3分钟

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 来执行数据库操作,实现了事务的隔离性。

四种引用

  1. 强引用
  2. 软引用
  3. 弱引用
  4. 虚引用

强引用

  如果一个对象具有强引用,就不会被回收,即使内存不住,也只会抛出 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("回收了");
        }
    }