并发面试题--什么是ThreadLocal本地局部变量?

本文作者:黄海燕,叩丁狼高级讲师。原创文章,转载请注明出处。

ThreadLocal是一个本地线程副本变量工具类,可以理解为线程的本地变量。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,实现了线程的数据隔离,是线程独有的所以本身就是线程安全,特别适用于各个线程依赖多个不同的变量值完成操作的场景。如:数据库连接、Session管理等。

1.1 ThreadLocal简单使用

1.1.1 ThreadLocal常用方法:

image.png

1.1.2 需求:简易数据库连接池

因为数据库连接Connection的创建和消耗非常浪费资源,所以我们就选择了数据库连接池,以空间换时间,事前创建一定数量的数据库连接,存放到一个池子中,有需要的时候拿来用,用完放回放回到池子中。并且在多线程中很可能一个线程在使用Connection进行数据库操作,而另外一个线程调用同一个Connection的close方法关闭链接。

public class DBUtil {
    //数据库连接池
    private static DataSource source;
    //为了保证同一个线程使用的连接操作和释放都是同一个Connection,需要使用ThreadLocal
    private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
    private static LinkedList<Connection> pool = new LinkedList<Connection>();
    private static int initSize = 10;
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            for (int i = 0;i<initSize;i++){
                Connection a = DriverManager.getConnection("jdbc:mysql://localhost:3306/shop?useSSL=false","root","admin");
                pool.addLast(a);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {
        //获取Connection对象
        Connection connection = pool.getFirst();
        //把Connection放进ThreadLocal里面
        local.set(connection);
        //返回Connection对象
        return connection;
    }
    //关闭数据库连接
    public static void closeConnection() {
        //从线程中拿到Connection对象
        Connection connection = local.get();
        if (connection != null) {
            //放回去,连接池
            pool.addLast(connection);
            //既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了
            local.remove();
        }
    }
}

注意:再多线程中为了保证每个线程的连接和释放的对象一致,需要使用ThreadLocal来保证连接的线程和释放的线程是同一个。

1.2 源码分析

ThreadLocal内存分析:


image.png

set方法源码分析:

public void set(T value) {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程中独有的ThreadLocalMap对象
    if (map != null)//map不为空
        //将key=当前ThreadLocal对象和value=传入的对象,放入到线程不共享的ThreadLocalMap中
        map.set(this, value);
    else
        createMap(t, value);//为空就创建ThreadLocalMap再存值
}

get方法源码分析:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//拿到线程中独有的ThreadLocalMap对象
    if (map != null) {
        //通过当前的ThreadLocal对象做为key拿到存放在线程中的ThreadLocalMap中的元素
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {//如果元素不为空
            T result = (T)e.value;//获取元素中的value,拿到之前存放在线程中的值
            return result;
        }
    }
    return setInitialValue();//如果拿不到结果,设置初始化值
}

1.3 ThreadLocal存在问题

image.png

我们可以通过源代码发现ThreadLocalMap中并不是实现了Map接口,而是自己写的一个Map结构。就是这个ThreadLocalMap存在两个问题,内存泄露无法释放内存和Hash冲突导致的运行效率低。

1.3.1 ThreadLocal的内存泄露

问题原因:上图,我们可以看到Entry中是继承了WeakReference类,WeakReference类是一个弱引用,生命周期只能存活到下次GC前,Entry构造器中发现只有key调用了父类构造器,也就是只规定了key为弱类型和key只能是ThreadLocal对象,并没有规定value,所以有可能导致key被GC回收了为null,而value还在ThreadLocalMap中被引用着,ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露,造成了资源的浪费。
解决方案:调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除

1.3.2 ThreadLocal的Hash冲突问题

image.png

问题原因:当发生元素存在Hash相同时,容器中发生Hash冲突,在HashMap中采用的是同槽位链表的方式解决的,就是如果hash值相同,将冲突的所有元素放到同一个链表中,而ThreadLocalMap是以线性探测的方式,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。通过上图红色部分发现,ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。ThreadLocalMap采用线性探测的方式如果出现hash冲突需要不停的计算,知道找到为止,这样解决Hash冲突的效率很低。如果出现很多ThreadLocal对象放入到Map中,hash出现冲突的几率就大,性能将大大降低。
解决方案:一个线程尽量少使用多个ThreadLocal对象。

想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容