ThreadLocal的使用场景及实现原理
1. 什么是ThreadLocal?
线程局部变量(通常,ThreadLocal变量是private static修饰的,此时ThreadLocal变量相当于成为了线程内部的全局变量)
2. 使用场景
变量在线程内部共享,线程间无关
再具体点,可以分为两类:
- 单例的对象中static属性,线程内共享,线程间无关;
- 工具类属性,线程内共享,线程间无关。
为什么这么说呢?下面看4个问题:
(1)对象为什么要是单例的?
如果对象不是单例的,那么大可以每次都new一个对象,然后对用到属性赋值就行,代码如下:
public class Service {
    private String key;
    void A() {
        // 代码实现,中间用到key
    }
    void B() {
        // 代码实现,中间用到key
    }
    // 省略get和set方法
}
在使用时,每个线程都new Service(),并对key赋值,然后调用其中的方法就行了,保证方法A和B用的key都是一个值。
(2)单例对象的属性共享
如果希望单例对象中的某个属性可以被共享,那么将属性声明为static就行了:
public class Service {
    private static String key;
    // 省略其他方法
}
上面的实现确实保证了所有方法都能使用key,然而,在多线程环境下,key是不安全的。
(3)单例对象在线程内属性共享,不同线程间相互不影响
这就轮到ThreadLocal上场了:
public class Service {
    private static ThreadLocal<String> key = new ThreadLocal<String>() {
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };
    public void A() {
        System.out.println("methodA: " + key.get());
        key.set("methodA: " + key.get());
    }
    public void B() {
        System.out.println("methodB: " + key.get());
    }
}
使用方式:
public class ThreadLocalTest {
    public static void main(String[] args) {
        final Service service = new Service(); //模拟单例对象的使用
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.A();
                service.B();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.A();
                service.B();
            }
        }).start();
    }
}
运行结果:
methodA: Thread-0
methodB: methodA: Thread-0
methodA: Thread-1
methodB: methodA: Thread-1
(4)工具类中线程共享,线程间无关
工具类的代码:
public final class XUtil {
    private static ThreadLocal<String> key = new ThreadLocal<String>();
    private XUtil() {
    }
    public static void A() {
        // 实现
    }
    public static void B() {
        // 实现
    }
}
在使用XUtil时,每个线程中key可以使用,不同线程间不受影响。
3. ThreadLocal的实现原理
为什么一个static的变量(即:类变量)可以做到:线程内共享,线程间无关?
看下ThreadLocal中的get源码:
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map!=null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e!=null)
            return (T)e.value;
    }
    return setInitialValue();
}
关键就在getMap()方法:
ThreadLocalMap getMap(Thread t) {
    return t.threadlocals;
}
取的是当前线程内部的threadLocals属性。
查看Thread类:
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal类中自定义的一个HashMap类。
原来数据就存在当前线程内部,自然就能做到线程内共享,线程间无关了。
接着看下set的源码:
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap();
    if(map != null)
        map.set(this, value);
    else
        createMap(t,value);
}
无论是map.set(this, value)还是createMap(t, value),最后都是将数据保存到当前线程中的那个HashMap中:将ThreadLocal变量作为key,value就是要保存的数据。
4. ThreadLocal的内存泄露
在前面看到数据最终是存在线程内部的一个Map中的:
ThreadLocal.ThreadLocalMap threadLocals = null;
且key是ThreadLocal变量的引用,在get方法:
ThreadLocalMap.Entry e = map.getEntry(this); // this为当前对象的引用
当ThreadLocal变量被销毁时,而当前线程又持有ThreadLocal的引用,那么ThreadLocal就不会被回收,导致内存泄露。
然而,编写JDK的大牛们考虑到了这个问题,因此将ThreadLocalMap的key设置为弱引用:
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal> {
        Object value;
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
}
对ThreadLocal变量的弱引用,在GC时,ThreadLocal变量就会被回收。
于是,在当前线程的本地变量HashMap中,原来ThreadLocal作为key的,现在变成null作为key了,该key-value变得不可访问了,如果当前线程一直不结束,那么value对应的对象就无法释放,也就是发生内存泄露了。
参考文献:
ThreadLocal的使用场景及实现原理的更多相关文章
- etcd:从应用场景到实现原理的全方位解读 转自infoq
		转自 infoq etcd:从应用场景到实现原理的全方位解读 http://www.infoq.com/cn/articles/etcd-interpretation-application-scen ... 
- [转帖]kafka入门:简介、使用场景、设计原理、主要配置及集群搭建
		kafka入门:简介.使用场景.设计原理.主要配置及集群搭建 http://www.aboutyun.com/thread-9341-1-1.html 还没看完 感觉挺好的. 问题导读: 1.zook ... 
- ThreadLocal的使用场景分析
		目录 一.ThreadLocal介绍 二.使用场景1——数据库事务问题 2.1 问题背景 2.2 方案1-修改接口传参 2.3 方案2-使用ThreadLocal 三.使用场景2——日志追踪问题 四. ... 
- volatile、ThreadLocal的使用场景和原理
		并发编程中的三个概念 原子性 一个或多个操作.要么全部执行完成并且执行过程不会被打断,要么不执行.最常见的例子:i++/i--操作.不是原子性操作,如果不做好同步性就容易造成线程安全问题. 可见性 多 ... 
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
		1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ... 
- 面试官:ThreadLocal的应用场景和注意事项有哪些?
		前言 ThreadLocal主要有如下2个作用 保证线程安全 在线程级别传递变量 保证线程安全 最近一个小伙伴把项目中封装的日期工具类用在多线程环境下居然出了问题,来看看怎么回事吧 日期转换的一个工具 ... 
- kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)
		问题导读: 1.zookeeper在kafka的作用是什么? 2.kafka中几乎不允许对消息进行"随机读写"的原因是什么? 3.kafka集群consumer和producer状 ... 
- ThreadLocal的设计与使用(原理篇)
		在jdk1.2推出时开始支持java.lang.ThreadLocal.在J2SE5.0中的声明为: public class ThreadLocal<T> exte ... 
- memcache的应用场景和实现原理
		面临的问题 对于高并发高访问的 Web应用程序来说,数据库存取瓶颈一直是个令人头疼的问题.特别当你的程序架构还是建立在单数据库模式,而一个数据池连接数峰 值已经达到500的时候,那你的程序运行离崩溃的 ... 
随机推荐
- P2731 骑马修栅栏  欧拉函数
			题目背景 Farmer John每年有很多栅栏要修理.他总是骑着马穿过每一个栅栏并修复它破损的地方. 题目描述 John是一个与其他农民一样懒的人.他讨厌骑马,因此从来不两次经过一个栅栏.你必须编一个 ... 
- Python基础(6)——装饰器
			装饰器: def w1(func): def inner(): # 验证1 # 验证2 # 验证3 return func() return inner @w1 def f1(): print 'f1 ... 
- QWidget设置背景颜色
			如果widget是子窗口首先要添加一句: this->setAttribute(Qt::WA_StyledBackground,true); this->setStyleSheet(&qu ... 
- rook 排错记录 + Orphaned pod found kube-controller-manager的日志输出
			1.查看rook-agent(重要)和mysql-wordpress 的日志,如下: MountVolume.SetUp failed for volume "pvc-f002e1fe-46 ... 
- 解决 在Android开发上使用KSOAP2上传大图片到服务器经常报错的问题
			原文首发我的主力博客 http://anforen.com/wp/2017/04/android_ksoap2_unexpected_type_position_end_document_null_j ... 
- LiveCharts文档-2FAQ
			原文:LiveCharts文档-2FAQ LiveCharts文档-2FAQ 原文链接 LiveCharts基于的平台有WPF,UWP,WinForms:语言是C#, FAQ: 我怎么转换一个char ... 
- 校内模拟赛 Attack's Fond Of LeTri
			Attack's Fond Of LeTri 题意: n个房子m条路径边的无向图,每个房子可以最终容纳b个人,初始有a个人,中途超过可以超过b个人,每条边有一个长度,经过一条边的时间花费为边的长度.求 ... 
- dotnetcore/CAP
			CAP带你轻松玩转Asp.Net Core消息队列 CAP是什么? CAP是由我们园子里的杨晓东大神开发出来的一套分布式事务的决绝方案,是.Net Core Community中的第一个千星项目(目前 ... 
- vim-plug 插件安装与操作
			安装 vim-plug curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/juneg ... 
- 关于Prometheus运维实践项目
			关于Promethues运维实践项目 1. 什么是Prometheus运维实践项目  是什么  Prometheus,普罗米修斯,是古希腊神话中为人间带来火种的神.  Prometheus运维实 ... 
