怎么用:
我们使用ThreadLocal主要通过三个方法, initValue(), set(),get() ;
initValue : 设置初始值(ThrealLocal 在初始化的时候会自动调用这个方法来设置初始值);
set: 设置值,重新赋值;
get : 获取值;
使用的时候一般有两种使用方法:
方法1: 在new的时候重写initVlue方法,设置初始值,然后通过get方法获取值,set方法重新赋值;
方法2: 在当前类的构造函数中通过set方法,设置初始值,然后通过get方法获取值,set方法重新赋值;
方法1代码:(测试类,demo,控制台打印结果)
测试类:
public class ThreadLocalTest implements Runnable{
//重写initValue方法初始化变量 private ThreadLocal<Integer> i = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; } }; //run方法
public void run() { this.run1(); } private void run1(){ for (int j = 0; j < 100; j++) { //使用get方法获取值 System.out.println( Thread.currentThread().getName() + " : "+ + j + " : " + i.get() + " : " +( j == i.get())); //调用set方法重新赋值 i.set( i.get() + 1); try { Thread.currentThread().sleep(1); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("睡眠问题"); } } }}
demo:
public class ThreadLocalDemo { public static void main(String[] args) { ThreadLocalTest test = new ThreadLocalTest(); for (int i = 0; i < 4 ; i++) {
//注意这里创建线程时传进去的runable是同一个ThreadLocalTest实例,也就是每个线程使用了该实例同一个成员变量,并在run方法中对该成员进行了并发操作.后面的结果我们会看到虽然进行了并发操作,但是数据却是安全的
Thread thread = newThread(test);
thread.setName("thread " + i);
thread.start(); } } }
结果:全部为true,也就是说并发情况下ThreadLocal确保了数据的安全性。
(通过打印语句中的 j == i.get() 来确定线程是否安全,结果全部为true)
thread 0 : 0 : 0 : true
thread 1 : 0 : 0 : true
thread 2 : 0 : 0 : true
thread 3 : 0 : 0 : true
thread 0 : 1 : 1 : true
thread 1 : 1 : 1 : true
thread 3 : 1 : 1 : true
thread 2 : 1 : 1 : true
thread 0 : 2 : 2 : true
。。。
thread 3 : 98 : 98 : true
thread 1 : 99 : 99 : true
thread 2 : 98 : 98 : true
thread 0 : 99 : 99 : true
thread 3 : 99 : 99 : true
thread 2 : 99 : 99 : true
方法2代码:(随后上传)
原理(浅层):
关键的类是: ThreadLocalMap (以ThreadLocal 为key , object为value )
这个类是ThreadLocal 的一个内部类 (是它的内部类,但却不是它的成员变量,只是局部变量;设计成内部类只因为该类无需被大范围使用。所以内部类这点不用关注,不是重点,设计成外部的类也完全可以,完全忽视它内部类的身份就可以了)。
同时也是Thread类中的一个成员变量(ThreadLocalMap threadLocals)(这个才是重点) 。
当往一个ThreadLocal对象中存值时,实际上是获取到当前线程,然后往当前线程的成员变量ThreadLocalMap threadLocals 中存值,key 为threadLocal, value 为要存的值。取值时也是获取当前线程,然后从其成员变量ThreadLocalMap threadLocals中取值。所以我们存的值是和当前线程绑定到了一起。
需要注意的是ThreadLocalMap和其他map一样是,可以存放多组 key value的,所以一个线程在执行过程中是一般是存放了多组 key value 的。
Thread类并没有对外提供ThreadLocalMap threadLocals的访问方式,所以我们并不能直接访问这个map。
ThreadLocal之所以能访问ThreadLocalMap threadLocals是因为它和Thread在同一个包下,并且ThreadLocalMap threadLocals的权限设置的是default。
ThreadLocalMap这个类是深入了解ThreadLocal的关键,深入研究就去研究ThreadLocalMap这个类,比如它的继承关系,引用强弱。它的有一个内部类,这个内部类继承了WeakReference,研究边知道ThreadLocalMap和其他的map不同,它根本没有实现map接口。
注意事项:
1: 如果是web项目,一定要注意在使用完ThrealLocal后及时的调用remove 方法及时销毁储存的对象;
因为对象和当前线程进行了绑定,而当前线程使用完之后可能不会被销毁而是被放到了线程池中,如果久而久之会造成内存溢出的.(这点是存在争议的要去研究一下)
fengxiu的动态datasource 似乎不用关心这个问题;
2: 如果是重写initValue 方法来进行赋值,要注意不能注入引用对象,多个对象将会引用这同一个对象,导致线程不安全;
3 : 如果是set方法来进行赋值,要注意不能注入引用对象,一方面是线程不安全,一方面是set后,再get可能get不到。
不清楚的问题:
1:
如果在一个方法中set了一个map对象,然后在另外一个方法中get,结果get到的是有时候是null(甚至全部为null)?
如果不是set方式初始化,而是init方法初始化map对象,那么就不会出现上面的问题?
如果init方法进行了初始化map,后面再交替使用get和set,那么会出现有时候get到initValue,有时候get到上次的set值?(这个问题的答案比较简单,get时先去获取上次set的值,如果get到的为null,再去获取initValue,所以get到的是set和initaValue交替)
参考连接:http://blog.51cto.com/zhangjunhd/53092
https://blog.youkuaiyun.com/wtjrenranwtj/article/details/50527565
2 一个子线程是如何获取到父线程的ThreadLocal变量?
是通过 Thread 中 inheritable 这个成员变量吗?