在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的方式有很多种,但是基本流程都是一样的,因为他的分析都是一步一步过来的,有因有果。
这篇文章主要从以下几个角度来分析理解
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ThreadLocal是什么?
ThreadLocal其实就是基于字面意思上理解就是线程局部变量,顾名思义则是线程安全的,因为都是局部了嘛。意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
二、ThreadLocal 如何使用
代码如下(示例):
public static void main(String[] args) {
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set("123");
new Thread(()->{
threadLocal.set("456");
System.out.println(Thread.currentThread().getName()+"----"+threadLocal.get());
},"t1").start();
System.out.println(Thread.currentThread().getName()+"----"+threadLocal.get());
}
运行结果:
main----123
t1----456
ThreadLocal的作用是每一个线程创建一个副本,从结果中我们看到,各个的线程互相持有不同的值。这是ThreadLocal的最基本的使用,其实他应用方面好多,比如Spring事务,多数据源等,看下面的代码你就对它的应用场景有一定的了解了
public class ConnectionManager {
private static Connection connection = null;
public static Connection getConnection() throws Exception{
if(connection == null){
connection = DriverManager.getConnection("jdbc","username","password");
}
return connection;
}
public static void closeConnection()throws Exception{
if(connection != null){
connection.close();
}
}
}
这是一个获取数据链接的方法,如果一个线程经常获取链接,关闭链接会给服务器造成很大的压力所以得采用连接池的思想:应用ThreadLocal 将数据库链接当作局部变量赋值给每个线程,这样每个线程在使用到的时候就不用频繁创建了,而且线程之间互不影响,也不会存在现在安全问题
三、ThreadLocal-set方法
代码如下(示例):
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
关键的几句介绍下:首先获取当前线程,通过当前线程获取ThreadLocalMap对象,如果这对象为空,则创建,执行createMap方法,否则执行set方法;
set方法 看到这个方法大家应该就懂了 ThreadLocal中的key是谁了吧!就是当前this对象,ThreadLocal,但是他的value可不是单纯的Object对象,而是一个ThreadLocalMap,这个对象可以说是ThreadLocal中的核心了
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
//将这个value包装ThreadLocalMap.entry对象
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
四、ThreadLocal-get方法
看完get方法你对ThreadLocal基本可以说是入门了,也知道了最终保存的value就是一个ThreadLocalMap对象中Entry属性。
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();
}
简单介绍下 setInitialValue方法,看上面的代码也知道他是在map对象为空的情况下,执行此方法
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;
}
最终 执行
protected T initialValue() {
return null;
}
这个方法是用来子类实现,初始化用。比如SimpleDateFormat 他不是一个线程安全的类,在多线程使用中会有问题,一般这样用它
ThreadLocal<Object> objectThreadLocal = new ThreadLocal<>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat();
}
};
SimpleDateFormat simpleDateFormat = (SimpleDateFormat)objectThreadLocal.get();
五、ThreadLocalMap
其实它的关键点都是 WeakReference ,弱引用对象,这里给大家普及下:强软弱虚
1、强引用:
比如 Object object=new Object(); new 创建出来的object 对象就是强引用。当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,是程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
2、软引用(SoftReference):
如果一个对象是软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3、弱引用(WeakReference):
弱引用和软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内容区域的过程中,一旦发现了只具有弱引用对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4、虚引用(PhantomReference):jvm和netty中使用,便于监控堆外内存
“虚引用”顾名思义,就是形同虚设的意思,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
六、ThreadLocal其他几个注意的点
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
七、面试相关问题解答
ThreadLocal是如何引起内存泄露呢(内存泄露:内存不使用没有及时被jvm回收,导致内存溢出)。其中它的Entry对象虽然被修饰为弱引用,但是还是会引发内存泄露。大家可以看上面的那张图理解,就是ThreadLocal设置为null后,虽然没有强引用了,但是此时的ThreadLocalMap对象是被当前线程持有的,所以不会回收。尤其是在线程池的使用过程中,线程是反复利用,不会释放。
写在最后,感谢点赞关注收藏转发
欢迎关注我的微信公众号 【猿之村】
来聊聊Java面试
加我的微信进一步交流和学习,微信手动搜索
【codeyuanzhicunup】添加即可
如有相关技术问题欢迎留言探讨,公众号主要用于技术分享,包括常见面试题剖析、以及源码解读、微服务框架、技术热点等。