源码浅析ThreadLocal类

Posted 加冰雪碧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码浅析ThreadLocal类相关的知识,希望对你有一定的参考价值。

ThreadLocal类的简单介绍

在并发编程中我们经常有共享资源的需求,而通过用锁的形式来保证资源的安全在多个方法共同读写同一资源时很难得到保证。再者,若全局共享一份资源,根据访问者的不同来处理不同的逻辑也将变的很困难。这时我们的ThreadLocal类就可以大显身手了,它可以为使用相同变量的每个线程都创建不同的副本。说起来总是很抽象,我们先来看一段实例代码:

public class ThreadLocalTest 
	private static ThreadLocal<String> myData = new ThreadLocal<String>();

	public static void main(String[] args) 
		myData.set("主线程存储内容");
		
		
		new Thread(new Runnable() 
			public void run() 
				myData.set("子线程1存储内容");
				System.out.println(myData.get());
			
		,"thread_1").start();
		
		
		new Thread(new Runnable() 
			public void run() 
				myData.set("子线程2存储内容");
				System.out.println(myData.get());
			
		,"thread_2").start();
		
		
		
		new Thread(new Runnable() 
			public void run() 
				System.out.println(myData.get());
			
		,"thread_3").start();
		
		System.out.println(myData.get());
		
	

输出:

子线程1存储内容
主线程存储内容
null
子线程2存储内容
代码很好理解,但是有没有感觉一点困惑?对,在不同的线程访问同一对象会返回不同的结果,并且如果在当前线程没有为这个对象来设置值的话,那么将默认返回null。显然ThreadLocal类为每一个线程单独储存了一份数据。那么其内部又是如何实现的?请看源码分析:


ThreadLocal源代码分析

为了更好的理解代码的意图也避免看一些细枝末节的源代码看的心烦,我们不去深究代码的一些细节,并且从我们第一次ThreadLocal的地方来入手。由上面的例子可知,我们第一次调用的ThreadLocal的方法是set,我们先来看一下它的代码:

 public void set(T value) 
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) 
            values = initializeValues(currentThread);
        
        values.put(this, value);
    
一步一步的来分析这段代码:

首先,获得了当前调用此方法的线程对象,然后调用了values方法,并将其传入,来看一下values方法的代码:

Values values(Thread current) 
        return current.localValues;
    
返回了线程类的localValues对象,我们接着打开线程类来找找这个对象:

ThreadLocal.Values localValues;
线程类中只声明了这个对象的引用,并且在之后的任何地方都没有用到它,那我们得到的这个values对象也就是null了。继续看,很顺利的进入到了if代码块中,我们来看一下initializeValues方法:

 Values initializeValues(Thread current) 
        return current.localValues = new Values();
    
为当前线程中的localValues引用new了对象。而且这个对象的类型是Values竟是ThreadLocal中的类:

/**
     * Per-thread map of ThreadLocal instances to values.
     */
    static class Values 
    
感觉非常的奇怪,为什么绕了一个这么大的圈子然后最后new出了一个静态的内部类?仔细想一下便不难理解,因为ThreadLocal要为每个线程单独的保存数据,那么这个保存数据的对象就不能作为它的对象存在,不然也就没啥意义了,那么最好的方法也就是作为线程类的对象存在。现在values也有了,然后调用了put方法,我们看一下源码:

        void put(ThreadLocal<?> key, Object value) 
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) 
                Object k = table[index];

                if (k == key.reference) 
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                

                if (k == null) 
                    if (firstTombstone == -1) 
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) 
                    firstTombstone = index;
                
            
        

我们挑重点的看一下,首先通过key和mask找到了一个位置(index),而后从table数组中找到这个位置中存放的数据。下面在if语句中判断这个数据是不是等于key.reference,那么这个key是什么,reference有事什么?可以看到,key是set方法传入的参数,而调用set方法是传入的参数是this,也就是当前对象本身。而reference参数可以看一下定义的源码:
 private final Reference<ThreadLocal<T>> reference
            = new WeakReference<ThreadLocal<T>>(this);

很显然我们之前没有设置过,现在的k并没有等于当前的reference,继续向下来看,当k为空时,给k设置了值,并且将我们想要放到ThreadLocal中的对象放到了table数组中index+1的位置。set方法现在应该是很清晰了,我们来看一下get:

public T get() 
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) 
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) 
                return (T) table[index + 1];
            
         else 
            values = initializeValues(currentThread);
        

        return (T) values.getAfterMiss(this);
    

有了刚才的分析,我们不难看懂上面的代码,它将我们刚才放进去的值取出来了。但是如果values为空的时候返回的是什么?我们来看一下getAfterMiss方法:

 Object getAfterMiss(ThreadLocal<?> key) 
            ...
            if (table[index] == null) 
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) 
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                

                // The table changed during initialValue().
                put(key, value);
                return value;
            
            ...
        

方法比较长,我截取出了其中我们想要的一段,如果有兴趣读者可以自己去细致分析。在代码段里显示返回了key.initialValue()方法的返回值,我们来看一下:

protected T initialValue() 
        return null;
    

这个返回了个null,不知道大家还记不记得最开始贴出的示例代码的运行结果,在没有set值时,get方法返回的是null,这也印证了我们的结论。



以上是关于源码浅析ThreadLocal类的主要内容,如果未能解决你的问题,请参考以下文章

源码浅析ThreadLocal类

阿里架构师浅析ThreadLocal源码——黄金分割数的使用

阿里架构师浅析ThreadLocal源码——黄金分割数的使用

浅析 Java ThreadLocal

Android ThreadLocal类浅析

浅析 ThreadLocal