一. ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证每个线程之间的变量相互独立。在线程内部,通过set,get方法来访问变量。
程序跑起来,就会产生一个线程。在这个线程里面会有一个context上下文,可以往context里面存放东西,然后再线程管理范围内获取到。
二. ThreadLocal简单使用
开启两个线程,在每个线程中设置本地变量的值,然后调用print方法打印当前本地变量的值。在打印之后,remove方法会删除本地内存中的变量。
static ThreadLocal<String> thread = new ThreadLocal<>();
static void print(String str){
System.out.println(str + ":" + thread.get());
thread.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
thread.set("localOne");
print("thread1");
System.out.println("after remove :" + thread.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
thread.set("localSecond");
print("thread2");
System.out.println("after remove : " + thread.get());
}
});
t1.start();
t2.start();
}
三. ThreadLocal实现原理
①ThreadLocal三个方法:get,set,remove以及内部类ThreadLocalMap
②ThreadLocal与Thread的关系:
Thread类中的threadLocals:
ThreadLocal中的内部类ThreadLocalMap,其是一个Entry
③ThreadLocal的操作过程
get方法:
getMap(t)返回当前线程的threadLocals,然后根据当前的ThreadLocal对象作为key获取ThreadLocalMap中的value,如果是首次进来返回setInitialValue();
从上面的分析中,可以看到,ThreadLocal的实现离不开ThreadLocalMap类,ThreadLocalMap类是ThreadLocal的静态内部类。每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。
这样的设计主要有以下几点优势:
这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能;
当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。
四. ThreadLocal使用不当的内存泄漏问题
什么是内存泄漏?简单的就是,东西放在内存中,但是忘记其放在哪里了,占用着一部分内存,但是不能回收。当这一部分的内容越来越多时,内存吃紧,最终导致服务器宕机。
ThreadLocal的内存分配:
①问题分析:
ThreadLocal作为key,存放在ThreadLocalMap里面,但是因为key被包装成弱引用,很容易导致内存泄漏。
java中的四种引用类型(强,软,弱,虚):
1. 强引用(strong reference)
默认的对象都是强引用类型。
String str = "love you"
如果JVM垃圾回收器 GC 可达性分析结果为可达,表示引用类型仍然被引用着,这类对象始终不会被垃圾回收器回收,即使JVM发生OOM也不会回收。而如果 GC 的可达性分析结果为不可达,那么在GC时会被回收。
2. 软引用(soft reference)
软引用是一种比强引用生命周期稍弱的一种引用类型。在JVM内存充足的情况下,软引用并不会被垃圾回收器回收,只有在JVM内存不足的情况下,才会被垃圾回收器回收。所以软引用一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉。
SoftReference<String> softReference = new SoftReference<String>(new String("www.threadlocal.cn"));
String web = softReference.get();
3. 弱引用(weak reference)
弱引用是一种比软引用生命周期更短的引用。它的生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。
@Test
public static void weakReference() {
//String str = new String("Love you");
WeakReference<String> weakReference = new WeakReference<>(new String("love you"));
System.gc();
if(weakReference.get() == null){
System.out.println("weakRefenrence已经被回收了");
}else{
System.out.println(weakReference.get());
}
}
4. 虚引用(phantom reference)
虚引用与前面的几种都不一样,这种引用类型不会影响对象的生命周期,所持有的引用就跟没持有一样,随时都能被GC回收。
需要注意的是,在使用虚引用时,必须和引用队列关联使用。在对象的垃圾回收过程中,如果GC发现一个对象还存在虚引用,则会把这个虚引用加入到与之关联的引用队列中。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象内存被回收之前采取必要的行动防止被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
PhantomReference<String> phantomReference = new PhantomReference<String>(new String("www.threadlocal.cn"), new ReferenceQueue<String>());
System.out.println(phantomReference.get());
②ThreadLocal的变量属性
ThreadLocal<String> thread = new ThreadLocal<>();
thread.set(10)
对于上面的代码,记住两点:
10不是放在thread里面,10和thread是两个独立存在的东西,不是包含关系。
10和thread是两个独立存在的变量,如果其中一个被清理,另外一个不受影响。
通过key-value的形式存在,key是thread,value是10.不能直接访问到10.必须通过thread来找到,也就是在map里,value要通过key来访问。
此时的thread是一个弱引用,很容易被gc,一旦thread被清理了,10就找不到了,从而造成内存泄漏。