THREAD_LOCAL 学习

ThreadLocal 简单使用

ThreadLocal 准确的说应该叫 ThreadLocalVar - 线程本地变量,其目的是使得该变量是线程隔离的,对其他的线程是不可见的。

ThreadLocal 类一共就 3 个公开的方法,set(T value)get()remove()

每一个线程都有一个存放线程本地变量的容器,通过 ThreadLocal.set() 将变量的引用保存到各线程的自己的一个容器中,在执行 ThreadLocal.get() 时,各线程从自己的容器中取出放进去的对象,因此取出来的是各线程自己中的对象,ThreadLocal 实例是作为这个容器的 key 来使用的。

源码分析

    public void set(T value){
        // 获取当前线程
        Thread t=Thread.currentThread();
        // 获取当前线程存放线程本地变量的容器
        ThreadLocalMap map=getMap(t);
        // 如果存在就直接 set,没有则创建并 set
        if(map!=null)
        map.set(this,value);
        else
        createMap(t,value);
        }

        ThreadLocalMap getMap(Thread t){
        // Thread 类中有一个 ThreadLocalMap 类型的成员变量
        // 但是该变量的维护是由 ThreadLocal 负责的
        return t.threadLocals;
        }

        void createMap(Thread t,T firstValue){
        t.threadLocals=new ThreadLocalMap(this,firstValue);
        }
    public T get(){
        Thread t=Thread.currentThread();
        ThreadLocalMap map=getMap(t);
        if(map!=null){
        ThreadLocalMap.Entry e=map.getEntry(this);
        if(e!=null){
@SuppressWarnings("unchecked")
                T result=(T)e.value;
                        return result;
                        }
                        }
                        // 如果没 set 就开始 get,上面的 map 为 null,
                        // 就会先初始化后,再获取,默认初始化的值是 null
                        return setInitialValue();
                        }

ThreadLocal 存在内存泄露?

是这样的,一般来说,存放 ThreadLocalMap 和 Thread 是同生死的,ThreadLocalMap 的 key 为 ThreadLocal 实例,value 为 存放的变量。当 Thread 生命周期结束,被 GC 回收时,ThreadLocalMap 也会被回收,所谓的内存泄露是指,在当前 Thread 还在使用期间,由于 ThreadLocalMap 的 key 是 弱引用,在发生 GC 时,可能被回收(此时 Thread 还没有被回收),既然 key 被回收,那 value 也就没有存在的意义了,但是此时的 value 由于强引用的存在,没有被回收。这就是人们说的 ThreadLocal 出现的内存泄露。但是当 Thread 被回收时,ThreadLocalMap 和 其中的 value 依然会被回收的。

所以只有在线程池中,使用了 ThreadLocal 才需要特别注意这个问题,因为核心线程是不会被销毁的,如果这些线程的 ThreadLocalMap 中存在用不到的 value,且由于 Thread 一直存在 value 不能被 回收,可以认为是真正发生了内存泄露。解决办法就是:使用完后主动的 remove 掉。

代码如下:

public class ThreadPoolTest {
    static class LocalVariable {
        private Long[] a = new Long[1024 * 1024];
    }

    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            10, 10,
            1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(() -> {
                ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();
                localVariable.set(new LocalVariable());
                System.out.println("use local varaible");
                //localVariable.remove(); 为了避免 ThreadLocalMap 中不在使用的 value 不能被及时的回收,造成内存泄露,可以在使用完后主动的 remove 掉。
            });
            Thread.sleep(1000);
        }
        System.out.println("pool execute over");
    }
}

142454

补充知识 - 强、软、弱、虚引用

Java中提供这四种引用类型主要有两个目的:第一是可以通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。

public class M {
    @Override
    protected void finalize() {
        // 重新该方法是为了观察实例有没有被垃圾回收器回收
        System.out.println("finalize");
    }
}

强引用

对象被引用时,不会被 GC 回收。

public class NormalReference {
    public static void main(String[] args) throws IOException {
        M m = new M();
        m = null;
        System.gc(); //DisableExplicitGC
        System.in.read();
    }
}

控制台打印 finalize ,说明到 m 置为 null 后,new M() 这个对象没有被引用,所以 GC 会清理掉。

软引用

软引用在内存充足时可能不会被 GC 回收,在内存不够时会被回收。

/**
 * 软引用是用来描述一些还有用但并非必须的对象。
 * 对于软引用关联着的对象,在系统将要发生 OOM 异常之前,将会把这些对象列进回收范围进行第二次回收。
 * 如果这次回收还没有足够的内存,才会抛出内存溢出异常。
 * 软引用非常适合缓存使用
 * VM options -Xms20M -Xmx20M
 */
public class SoftReference {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]); //10M
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(m.get()); //经历  GC 后对象仍然存在,不会被回收。

        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
        byte[] b = new byte[1024 * 1024 * 10]; //10M 此时堆内存不足 10M 会回收软引用,如果此时堆内存依然不足,则抛 OOM。
        System.out.println(m.get());
    }
}

弱引用

发生 GC 时就会被回收。

/**
 * 弱引用遭到 gc 就会回收
 * 一般使用在容器中,弱引用的主要用途是,一旦指向对象
 * 的强引用断开,就无需关心该对象不会被回收。
 * <p>
 * WeakHashMap
 */
public class WeakReference {
    public static void main(String[] args) throws IOException {
        WeakReference<M> m = new WeakReference<>(new M());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());

        System.in.read();
    }
}

001723

public class WeakReference1 {
    public static void main(String[] args) throws IOException {
        ThreadLocal<M> tl = new ThreadLocal<>();
        tl.set(new M());
        tl.remove(); // 用完养成 remove 的好习惯。
        System.gc();
        System.in.read();
    }
}

虚引用

未知,使用很少。



参考资料