线程封闭之栈封闭和ThreadLocal

线程封闭

  在多线程的环境中,咱们常常使用锁来保证线程的安全,可是对于每一个线程都要用的资源使用锁的话那么程序执行的效率就会受到影响,这个时候能够把这些资源变成线程封闭的形式。html

 一、栈封闭

  所谓的栈封闭其实就是使用局部变量存放资源,咱们知道局部变量在内存中是存放在虚拟机栈中,而栈又是每一个线程私有独立的,因此这样能够保证线程的安全。小程序

 二、ThreadLocal

  咱们先看ThreadLocal和线程Thread的关系图。安全

  

   再看下ThreadLocal的操做,以get为例多线程

public T get() {        // 当前线程        Thread t = Thread.currentThread();   // 拿到当前线程的threadLocalMap,即上图中的map引用        ThreadLocalMap map = getMap(t);        if (map != null) {            // 拿到当前ThreadLocal为Key对应的Entry,里面作了防止内存泄漏的处理            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        // 若是为null设置默认值        return setInitialValue();    }

  如上面get方法的源码所示,在调用threadLocal.get()方法的时候,threadLocal拿到当前线程中ThreadLocalMap中以threadLocal自身为key对应的entry,在这个getEntry方法中里面作了内存泄漏的处理,大概处理逻辑就是若是threadLocal对应的Entry为null的话,让这个entry的value为null而且map中threadLocal对应下标置null,若是不为null的话返回,不然的话则调用默认值方法setInitialValue()ide

private T setInitialValue() {        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;    }  // 默认null实现 protected T initialValue() {        return null;  }

  setInitialValue()方法逻辑比较简单,这里很少赘述,值得注意的是里面调用的initialValue(),并无任何的实现,因此咱们使用threadLocal的时候通常都会选择重写实现这个方法。测试

// 这里main方法测试,因此用static修饰,会延长threadLocal的生命周期,有内存泄漏的风险,通常做为成员变量就足够了 public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){        @Override        protected String initialValue() {            return "init string from initialValue method";        }    };    public static void main(String[] args) throws InterruptedException {        // 未放入value直接调用get        System.err.println("invoke get before any set:" + threadLocal.get());        threadLocal.set("test");        System.err.println("before thread start : " + threadLocal.get());        new Thread(() -> {            // 对相同的threadLocal对象放入值            threadLocal.set("test in thread");            System.err.println("In thread[" + Thread.currentThread().getName() + "] threadLocal value : " + threadLocal.get());        }).start();        TimeUnit.SECONDS.sleep(1);        // 证实threadLocal中的value不在线程中共享        System.err.println("after thread value : " + threadLocal.get());    }result:  
  结合这个小程序和上面的图就能够对threadLocal有一个大概的理解了。其余的方法如set、remove等方法都大同小异,能够结合图片去看源码,这里再也不赘述。

  关于内存泄漏的问题

    一、在threadLocal的get、set、remove方法中,其对自己可能发生的内存泄漏都作了处理,逻辑上面也提到若是对应entry为null,将其value置null,将map中对应下标引用置null。this

    二、而对于threadLocal中这个对象的泄漏来讲,则是采用弱引用的方式来实现,在上面的图中,我用虚线来表示弱引用,弱引用的意思是在JVM进行垃圾回收的时候这个引用会被回收(不管内存足够与否);试想一下,若是使用强引用而且栈中的引用消失了,那么在线程结束以前这个threadLocal对象不会被回收且没法访问,也就是形成内存泄漏。spa

 三、Java四种引用的简要概述

  上面在ThreadLocal提到了弱引用,这里顺便简单的说下Java中的四种引用。线程

  1. 强引用:指new出来的对象,通常没有特别申明的对象都是强引用。这种对象只有在GCroots找不到它的时候才会被回收。
  2. 软引用(SoftReference的子类):GC后内存不足的状况将只有这种引用的对象回收。
  3. 弱引用(WeakReference的子类):GC时回收只有此引用的对象(不管内存是否不足)。
  4. 虚引用(PhantomReference子类):没有特别的功能,相似一个追踪符,配合引用队列来记录对象什么时候被回收。(实际上这四种引用均可以配合引用队列使用,只要在构造方法中传入须要关联的引用队列就行,在对象调用finalize方法的时候会被写入到队列当中)
如有不正之处,望指出!
相关文章
相关标签/搜索