ThreadLocal线程本地变量

概念

ThreadLoacl主要是用来做线程间共享但又不关联的共享变量的存储。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,也就是只是共用这个变量以及该变量的初始值,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

  

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	public void test(){
        ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
        ExecutorService service = Executors.newFixedThreadPool(3);
        for(int i=0;i<3;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                	Integer num = local.get();
                	Integer init = num;
                	while(++num<10); 
                	System.out.println("当前线程为:" + Thread.currentThread().getName() + ",初始值为:"+init+",结果数据为:" + num);
                }
            });
        }
		service.shutdown();
    }
	
	public static void main(String[] args) {
		new ThreadLocalTest().test();
	}
}

  

当前线程为:pool-1-thread-1,初始值为:0,结果数据为:10
当前线程为:pool-1-thread-3,初始值为:0,结果数据为:10
当前线程为:pool-1-thread-2,初始值为:0,结果数据为:10

 通过测试代码可以看到,虽然三个线程使用了同一个ThreadLocal变量,并各自进行加到10操作,但是三个线程之间并没有相互影响,而只是共享了变量初始值。

 

ThreadLocal源码分析

1. get()

首先查看ThreadLocal构造方法,发现什么也没做,那么查看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方法首先取得当前线程对象,接着借此调用getMap方法返回ThreadLocalMap对象。
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
 而getMap方法最终是从当前线程中取得ThreadLocalMap实例,查看线程类:
/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
 发现它是线程类的实例成员,也就是说,每一个线程都有自己独立的ThreadLocalMap实例。
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;
}
 其中initialValue()方法就是我们重写过的方法,在上面的例子中我们返回的是0.由于是第一次执行,getMap(t)方法方的是null,所以接着看createMap方法:
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}
终于创建了当前线程t的ThreadLocalMap,其中参数this为当前ThreadLocal实例,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);
}
 在ThreadLocalMap构造方法中,创建了一个类似Map的对象,并且以ThreadLocal实例为key,initialValue()方法的返回值为value。所以每一个线程都有一个独立的这样的ThreadLocalMap对象将ThreadLocal实例与其值关联起来。 接下来,如果 第二次调用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();
    }
 由于getMap(t)不再返回null,所以进入if块,然后以当前ThreadLocal实例为Key,取得当前线程的ThreadLocalMap中对应的值,当然如果发现不存在这样的键值对entry,那么还是进行执行setInitialValue. 2. set(T value) 这个方法就是重新设置每一个线程的本地ThreadLocal变量的值
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
 这里取得当前线程,然后根据当前的thradLocal实例取得其map。然后重新设置 map.set(this, value);这时这个线程的thradLocal里的变量副本就被重新设置值了! 3. remove() 就是清空ThreadLocalMap里的key-value键值对,这样一来。下次再调用get时又会调用initialValue这个方法返回设置的初始值.
public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}
  总结: 1、每个线程都有自己的局部变量 每个线程都有一个独立于其他线程的上下文ThreadLocalMap来保存这个变量。 2、独立于变量的初始化副本 ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。 3、状态与某一个线程相关联 ThreadLocal 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在, 而是为了方便每个线程处理自己的状态而引入的一个机制。

猜你喜欢

转载自pzh9527.iteye.com/blog/2384638