• ThreadLocal


    1、基本概念

    ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

    2、使用场景

    1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束;

    2、线程间数据隔离;

    3、进行事务操作,用于存储线程事务信息;

    4、数据库连接,Session会话管理。

      频繁的创建和关闭Connection是一件非常耗费资源的操作,所以需要创建数据库连接池,而数据库连接池的连接的管理任务就交由ThreadLocal来进行管理。为什么交给它来管理呢??ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!

    //保证一个线程当中只有一个连接对象,且修饰符为private,即只能在本类中访问
    private ThreadLocal<Connection> connHolder = new ThreadLocal<Connection>();
    /**
     * 获取连接对象,建立与HBASE的连接
     */
    protected synchronized Connection getConnection() throws IOException {
        //当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,
        //如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
        
        //从线程中拿到连接对象
        Connection conn  = connHolder.get();
        if ( conn == null ){
            // 1.获取配置文件信息
            Configuration conf = HBaseConfiguration.create();
            // 2.建立连接,获取connection对象
            conn = ConnectionFactory.createConnection(conf);
            connHolder.set(conn);
        }
        return conn;
    }
    /**
    * 关闭连接
    * 这里不是真的把连接关了,只是将该连接归还给连接池
    * @throws IOException
    */
     protected void end() throws IOException {
    ​
            Admin admin = getAdmin();
            if (admin != null){
                admin.close();
                adminHolder.remove();
            }
            Connection conn = getConnection();
            if (conn != null){
                conn.close();
                //既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了
                connHolder.remove();
            }
     }

    3、方法

    (1)set方法

    设置ThreadLocal的值,

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

      先获取当前线程对象,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。分析一下ThreadLocalMap:

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    ​
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ……
       }

      ThreadLocalMap本身是ThreadLocal的一个静态内部类,在它的内部又定义了一个静态内部类Entry用于存储数据,存储数据的方式是key-value的形式,其中ThreadLocal为key,我们存储ThreadLocal中的线程变量为value。getMap方法是根据传入的当前的线程对象返回当前线程对象的成员变量threadLocals。

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

      总结一下,ThreadLocal并不是直接存储我们设置的值,而是通过一个ThreadLocalMap来存储在一个ThreadLocal中的值。其中,一个线程对象对应一个ThreadLocalMap(Thread类内部维护着一个ThreadLocalMap的引用)。ThreadLocalMap本质是一个Entry类,其中以当前的ThreadLocal对象作为key,我们设置的值作为value进行存储,因此ThreadLocal起到的作用是key。

    (2)get方法

      返回此线程局部变量的当前线程副本中的值。也就是根据当前的ThreadLocal对象获取存储在ThreadLocal中的value值。

    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;
            }
        }
        return setInitialValue();
    }

      首先获取当前线程,然后调用getMap方法获取一个ThreadLocalMap,如果map不为null,那就使用当前线程作为ThreadLocalMap的Entry的键,然后值就作为相应的的值,如果没有那就通过setInitialValue方法设置一个初始值。

    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;
    }

      initialValue方法返回的值是null,初始化的时候,赋予当前线程对象的对应于的ThreadLocalMap中的value值是null。

    (3)remove方法

    删除此线程局部变量的当前线程值。

    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

    类似于map中的remove操作。

    4、总结

    (1)每个Thread维护着一个ThreadLocalMap的引用

    ThreadLocal.ThreadLocalMap threadLocals = null;

    (2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

    (3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

    (4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

    (5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

    (6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

    5、ThreadLocal内存泄漏的问题

    通过之前的源码,我们知道,Thread中有一个维护一个map即ThreadLocalMap,而ThreadLocalMap的key是ThreadLocal对象,值是我们通过ThreadLocal的set方法设置的;ThreadLocal是继承了WeakReference是一个弱引用,当为null时,会被当成垃圾回收。

    但是,重点来了,如果我们ThreadLocal是null了,此时要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

    解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

    参考:

    https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc

  • 相关阅读:
    classic problem: select sortjava
    【转】排序算法复习(Java实现)(二): 归并排序,堆排序,桶式排序,基数排序
    【转】排序算法复习(Java实现)(一): 插入,冒泡,选择,Shell,快速排序
    classic problem: 100 horse and 100 dan
    good chocolate
    【转】Java内存模型 http://blog.csdn.net/silentbalanceyh
    http header/ eclipse package path
    design patterns: factory and abstractFactory
    阅读笔记
    提取Dump文件的Callstack,创建windbg的一个扩展应用
  • 原文地址:https://www.cnblogs.com/yxym2016/p/13622059.html
Copyright © 2020-2023  润新知