zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    POJ 2175 Evacuation Plan 费用流 负圈定理
    POJ 2983 Is the Information Reliable? 差分约束
    codeforces 420B Online Meeting
    POJ 3181 Dollar Dayz DP
    POJ Ant Counting DP
    POJ 1742 Coins DP 01背包
    中国儒学史
    产品思维30讲
    Java多线程编程核心技术
    编写高质量代码:改善Java程序的151个建议
  • 原文地址:https://www.cnblogs.com/yxym2016/p/13622059.html
Copyright © 2011-2022 走看看