首先如果多线程访问一个共享变量的时候,会出现并发的情况,导致线程不安全,为了解决这一情况,有一种方法是采用加锁的方式(如synchronized等),这样保证每次访问变量的时候只有一个线程,其他线程都在等待。但是这样的话,会使得执行效率降低。因此有了第二种方法:采用threadlocal。
从名字我们就可以看到ThreadLocal
叫做本地线程变量,意思是说,ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
简单来说就是threadlocal会给要访问共享变量的每一个线程创建一个共享变量的本地副本,线程直接访问自己的本地副本即可,就不会出现不安全的情况了。
ThreadLocal的作用是给每一个线程创建一个副本,请看set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//这里的this是当前调用set方法的threadlocal实例
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
调用当期线程t,返回当前线程t中的成员变量threadLocals。而threadLocals其实就是ThreadLocalMap。
向ThreadLocal进行set值的操作,实际上是向当前线程对象中的****ThreadLocalMap****存入值,ThreadLocalMap我们可以简单的理解成一个Map,而向这个Map存值的key就是ThreadLocal实例本身。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
那么我们再看看ThreadLocalMap做了什么?
当createMap的时候
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//16
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
当new Entry的时候
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
它将ThreadLocal作为key值存了进去。
ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
首先,在每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里查找。
(1)每个Thread维护着一个ThreadLocalMap的引用
(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
(3)使用ThreadLocal变量创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
理解:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
该处的threadLocals来自:当前线程的threadLocals变量,但是该变量是ThreadLocal的。如下图的第二个图
(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
(6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
解决线程不安全的问题
实现在线程级别传递变量
存在的问题:容易导致内存泄漏
①Memory overflow:内存溢出,没有足够的内存提供申请者使用。
②Memory leak:内存泄漏,程序申请内存后,无法释放已申请的内存空间,内存泄漏的堆积终将导致内存溢出。
类型 回收时间 应用场景
强引用 一直存活,除非GC Roots不可达 所有程序的场景,基本对象,自定义对象等
软引用 内存不足时会被回收 一般用在对内存非常敏感的资源上,用作缓存的场景比较 多,例如:网页缓存、图片缓存
弱引用 只能存活到下一次GC前 生命周期很短的对象,例如ThreadLocal中的Key
虚引用 随时会被回收,创建了可能很快就会被回收 可能被JVM团队内部用来跟踪JVM的垃圾回收活动
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
从上图中可以看出,hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部**强引用**时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
> Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
线程迟迟不结束的情况:
线程执行结束之后,被放在了线程池中没有被销毁。
ThreadLocal自身并不储存值,而是作为一个key来让线程从ThreadLocal获取value。Entry是中的key是弱引用,所以jvm在垃圾回收时如果外部没有强引用来引用它,ThreadLocal必然会被回收。但是,作为ThreadLocalMap的key,ThreadLocal被回收后,ThreadLocalMap就会存在null,但value不为null的Entry。若当前线程一直不结束,可能是作为线程池中的一员,线程结束后不被销毁,或者分配(当前线程又创建了ThreadLocal对象)使用了又不再调用get/set方法,就可能引发内存泄漏。其次,就算线程结束了,操作系统在回收线程或进程的时候不是一定杀死线程或进程的,在繁忙的时候,只会清除线程或进程数据的操作,重复使用线程或进程(线程id可能不变导致内存泄漏)。因此,key弱引用并不是导致内存泄漏的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,并且没有手动删除对应value。
那么,为什么要将Entry中的key设为弱引用?相反,设置为弱引用的key能预防大多数内存泄漏的情况。如果key 使用强引用,引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。如果key为弱引用,引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被GC回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
总:使用在多线程场景下!
Session
会话管理。1.针对线程不安全的变量进行处理(SimpleDateFormat)保证线程安全
2.解决共享参数问题(数据隔离)
ThreadLocal 也常用在多线程环境中,某个方法处理一个业务,需要递归依赖其他方法时,而要在这些方法中共享参数的问题。
例如有方法 a(),在该方法中调用了方法b(),而在方法 b() 中又调用了方法 c(),即 a–>b—>c。如果 a,b,c 现在都需要使用一个字符串参数 args。
常用的做法是 a(String args)–>b(String args)—c(String args)。但是使用ThreadLocal,可以用另外一种方式解决:
在某个接口中定义一个ThreadLocal 对象
方法 a()、b()、c() 所在的类实现该接口
在方法 a()中,使用 threadLocal.set(String args) 把 args 参数放入 ThreadLocal 中
方法 b()、c() 可以在不用传参数的前提下,在方法体中使用 threadLocal.get() 方法就可以得到 args 参数
1.HibernateUtil工具类
2.不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性
3.spring的事务隔离源码
用于验证threadlocal的set值,是给每一个线程的副本进行了set操作
public class ThreadLocalTest {
private static ThreadLocal<Integer> num = new ThreadLocal<Integer>() {
// 重写这个方法,可以修改“num”的初始值,默认是null
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread one=new Thread(new Runnable() {
@Override
public void run() {
num.set(1);
System.out.println("one.num="+num.get());
}
});
one.start();
Thread two=new Thread(new Runnable() {
@Override
public void run() {
num.set(2);
System.out.println("two.num="+num.get());
}
});
two.start();
try{
Thread.sleep(500);
System.out.println("num="+num.get());
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 程序结果重点看的是主线程输出的是0,如果是一个普通变量,在一号线程和二号线程中将普通变量设置为1和2,那么在一二号线程执行完毕后在打印这个变量,输出的值肯定是1或者2(到底输出哪一个由操作系统的线程调度逻辑有关)。但使用ThreadLocal变量通过两个线程赋值后,在主线程程中输出的却是初始值0。在这也就是为什么“一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的”,每个线程都只能看到自己线程的值,这也就是ThreadLocal的核心作用:实现线程范围的局部变量。
*
*
*
* 也就是说,想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了,获取ThreadLocal的值时同样也是这个道理。这也就是为什么ThreadLocal可以实现线程之间隔离的原因了。
*
*/
}
one.num=1
two.num=2
num=0