1.结构分析
2.源码分析
3.使用和实验
ThreadLocal提供了局部变量,主要是通过这个类,将一些线程需要的,本来可能是各个线程共享的变量(Connection),或者是约定好的初始变量(Integer i=0),变成线程自己的私有变量。
即本来许多线程共享一个数据库连接Connection对象,这种情况,如果开同步会造成大量线程阻塞,如果不开则会有一个线程刚开启Connection另一个线程就关闭的现象,于是,用ThreadLocal,每个线程拥有一个Connection对象,自己开自己的关自己的。这就是ThreadLocal的用法。
那么主要问题是,怎么通过ThreadLocal给每个线程分配线程本地变量,即是通过重写ThreadLocal的方法。
class ThreadLocal
{
protected T initialValue() //(3)
{ return null; }
//注释部分可以先不看
private T setInitialValue()
{
T value = initialValue();//(2)
/*Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);*/
return value;
}
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();//(1)
}
}
对于我们想要设置成为线程本地变量的这个T类型的变量,我们的获得方法,就是get(),当调用get()后,看代码,前两行可以理解成一个寻找操作,然后,如果没找到,就到(1),返回设置初始化的值。
然后看到这个方法,到(2),即将初始化的那个返回拿过来,然后进行了一个设置工作,这部分展示不管,反正最后返回的就是initialValue()的返回值。
然后到(3),这里默认返回null,那么我们只要重写这个方法,然后返回我们想要的初始值,那么就可以在get()这得到了。
比如:
Class Test extends Thread
{
private ThreadLocal local = new ThreadLocal
{
protected Connection initialValue()
{
Class.forName("com.mysql.jdbc.Driver");
return Driver.getConnection("jdbc:mysql://localhost:3306/db_name","user","psd");
}
}
public void run()
{
Connection con = local.get();
}
}
这个ThreadLocal对象可以写在别的类里,然后定义成public static,在每个线程中调用get(),然后会带着需要的泛型变量T,存储到不同线程里,要了解它的存储形式,需要继续了解一些结构。
这是一个写在ThreadLocal里的内部类,显然它是一个Map的实现类,然后它的节点类Entry
显然,底层的存储是通过ThreadLocalMap来实现的,真正的线程本地变量是存储在ThreadLocalMap实例对象中的,并且是在这个Map对象的某个节点Entry上的。
而Entry中,key是ThreadLocal变量,value才是线程本地变量。因此是一个ThreadLocal对象对应一个value变量。
实际上,每一个线程都有一个ThreadLocalMap实例对象,也就是在Thread类里就有这么一个成员变量threadLocals。
刚才说了,ThreadLocal一般会写在某个类(A)里,写成public static,那么它实际上就是一个静态类对象了,在这个ThreadLocal类中根据需求重写好initialValue()方法后,就可以通过get()来得到初始值。
那么现在我有了一个ThreadLocal对象,A.local,与起初始化值con(通过get()获得),当在任意线程(B)调用A.local.get()时,现在可以来看我刚才注释掉的内容。
用文字来形容,则是,在get()方法里,先获得当前线程(B),然后获得B中的threadLocals,在使用这个ThreadLocalMap变量时,肯定需要做判空和初始化这个Map的操作,当它完成后,我们就把Entry
了解完结构,再来简单分析一下ThreadLocal的具体实现,只说在ThreadLocal上的代码。
class ThreadLocal
{
public ThreadLocal();
protected T initialValue();
private T setInitialValue();
public T get() ;
public void set(T value);
public void remove() ;
ThreadLocalMap getMap(Thread t);
void createMap(Thread t, T firstValue);
}
public T get() {
Thread t = Thread.currentThread();//(1)
ThreadLocalMap map = getMap(t);//(2)
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//(3)
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
(1)获得当前线程,(2)获得当前线程的ThreadLocalMap对象threadLocals
接下来,如果map不为空,那就在这个map里找
如过找到了,e不为空,那么就是有这个键值对,就可以得到value值,做下转换就返回result。
如果找不到节点,或者map都没被初始化,(Thread里threadLocals初始为null),那么进入setInitialValue()方法。
然后,最终都会返回这个ThreadLocal设置的对应线程本地变量。
private T setInitialValue() {
T value = initialValue();//(1)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//(2)
else
createMap(t, value);//(3)
return value;
}
当需要用到setInitialValue()方法时,即线程的map没初始化或者map里没相应节点,这时候都需要新建节点,因此需要初始值,通过(1)获得。
接下来,又是获得线程与线程的map,因此每个操作其实都是操纵的当前线程与它的map。
map非空时,只需要将
map为空时,需要初始化当前线程的threadLocals变量并且添加第一个节点,即(3)
createMap(Thread,T)是ThreadLocal的方法,下面分析,作用即是初始化传入Thread线程的threadLocals,并添加第一个节点。
protected T initialValue() {
return null;
}
现在再来看initialValue(),显然,具体实现上,就只需要重写这个方法了。
如果要实现一个返回自增不重复ID的ThreadLocal,那么就是这样写:
public static ThreadLocal local = new ThreadLocal(){
private AtomicInteger i = new AtomicInteger(1);
protected Integer initialValue()
{
return i.getAndIncrement();
}
};
类似数据库连接那样的,那就是这样的:
public static ThreadLocal local = new ThreadLocal{
protected Connection initialValue()
{
Class.forName("com.mysql.jdbc.Driver");
return Driver.getConnection("jdbc:mysql://localhost:3306/db_name","user","psd");
}
};
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
获得当前线程的ThreadLocalMap是写在ThreadLocal里的,显然这个已经很好理解了。
Thread.currentThread()获得当前线程,然后返回它的threadLocals就行,其实直接用也行,这里只是加了个方法的包装。
Thread,TheadLocal同处于java.lang包下,Thread中threadLocals无访问限制符,即default,因此同包可见。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
显然,这个方法用于创建一个含有
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
每一个方法基本都有获得线程t和它的map,然后这个set的作用相当于setInitialValue()+initialValue(),当调用get()时,是map中找不到当前ThreadLocal对象的key值和map没有初始化时,才用setInitialValue()+initialValue()初始化,因此当完成这一步时,map一定初始化好,节点也一定存在。
而set也是相同的作用,只是它是主动调用的,同样确保,如果map不存在,则初始化map并添加节点。如果map存在,则添加键值对,而如果用set()的话,就会存在value值覆盖的问题。
如果在map中,以当前ThreadLocal对象为key的键值对已存在,那么set()后,会用set(T value)参数中的value来更新这个键值对中的value值。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
获得当前线程的map,如果非空,则删除以当前ThreadLocal对象为key值的节点。
ThreadLocalMap中的remove()操纵中涉及到Map的遍历,节点的删除。
remove()之后,确保了当前线程的map中无以this为key的节点。
其实说明了initialValue()的重写方法后,就是靠这个来使用ThreadLocal了。
那么先写一段用ThreadLocal来实现为多线程添加递增不重复ID的代码:
class A
{
public ThreadLocal local = new ThreadLocal()
{
private AtomicInteger i = new AtomicInteger(1);
protected Integer initialValue()
{
return i.getAndIncrement();
}
};
}
public class Main
{
public static void main(String[] args)throws Exception
{
A a = new A();
A b = new A();
for(int i=0;i<3;i++)
{
Thread t = new Thread(new Runnable() {
public void run()
{
Integer I = a.local.get();
System.out.println(I);
}
});
t.start();
t.join();
}
for(int i=0;i<3;i++)
{
Thread t = new Thread(new Runnable() {
public void run()
{
Integer I = a.local.get();//1
// Integer I = b.local.get();//2
System.out.println(I);
}
});
t.start();
t.join();
}
}
}
//使用注释行1,则六个线程都是存的a.local,其原子变量也增到了6,因此输出1~6
//使用注释行2,则前三个存a.local,后三个存b.local,输出1~3,1~3
并且,线程的threadLocals是Map,因此是能存多个
总结:
ThreadLocal和同步机制对线程来说是两种处理数据的方法,当多线程之间需要共享数据的情况下,就需要用同步机制来保证线程安全。
而当多线程直接没有共享数据的情况下,又需要操纵一些公有的数据,因此可以自定义一个ThreadLocal的实现类,按照自己的需求和数据的类型,来重写initialValue(),然后存储到线程自己的threadLocals这个Map中,因此会被叫做线程本地变量。
多用于解决数据库连接、Session管理。