ThreadLocal类的源码分析与应用场景

前言

在实习的过程中项目有用到TreadLocal类,主要就是在日志控制中,通过一个bean->RequestLog自定义类来保存request请求的参数,然后在控制台中打印这些请求参数。在封装请求的参数bean中可以维护一个静态的TreadLocal对象,将每一次请求也就是不同的线程维护自己线程内的一个requestLog实例对象,封装不同的request对象中的请求参数,然后在控制台打印这些参数日志,实现日志记录。

本文将通过简单分析源码的过程来加深对ThreadLocal类的理解,方便日后使用

ThreadLocal部分源码

这里主要介绍了get和set方法,因为一般的项目应用中用的最多的也就保存和获取值了,如果用到其他的方法,我再添加进来。

先来看看set(T value)方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

解析下set方法:
1.首先会获取当前正在运行的线程
2.通过getMap获取当前线程实例下的map集合ThreadLocal.ThreadLocalMap
3.判断该map是否为null
3.1如果不为null,就直接map.set(this,value)当前threadLocal实例为key
3.2如果为null,就new ThreadLOcalMap(this,firstValue),会做一些初始化entry数组的工作。

第一步很简单,获取当前线程,第二部getMap的源码如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

其实是TreadLocal类的内部维护了一个静态的内部类ThreadLocalMap类。然后上面的方法返回t.threadLocals.其实是线程类Tread中的成员变量。我们再来看看Thread类的部分源码:

public Class Thread{
ThreadLocal.ThreadLocalMap threadLocals = null;
}

也就是说每个线程都维护了自己实例对象中的map集合,将作为set方法值得容器保存在各自的线程中。
第三步中 map.set(this, value);其实this值得是当前TreadLocal实例,一般在实际应用中都会维护一个静态同一个TreadLocal作为map的key,值作为需要保存的值保存到map中。
如果map为null,那么会创建一个初始化map, createMap(t, value);跟hashMap实现类似,内部会维护一个entry,数组 ,初始化数组的大小等操作。源码如下:

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

下面来看一看get()方法的部分源码:

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();
}

解析下get方法:
1.首先会先获取当前线程。
2.获取当前线程的map。
3.判断map是否为null
3.1如果不为null,获取map的entry(this),根据当前的threadLocal实例作为key,获取entry,然后e.value获取值,返回。
3.2如果map为null,return 初始化value的值。
3.2.1再次获取map,判断不是null,设置map(this,null);
3.2.2为null,new map(this,null);
3.3最后返回null;

主要看一下map为null的情况吧,返回null:

    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;
    }

相信看完了应该都会明白的,这里不再详细说明了。不明白的可以私信我。

ThreadLocal类总结

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

其实说了这么多归纳一下也就两点:

1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

应用场景

hibernate中典型的ThreadLocal的应用:

private static final ThreadLocal threadSession = new ThreadLocal();  

public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
} 

可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。
显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。

还有应用场景就是我在项目中运用ThreadLocal类来维护不用的请求线程保存各自的request请求参数,然后进行日志的打印。可以自行实现!

猜你喜欢

转载自blog.csdn.net/nwpu_geeker/article/details/80367621