理解ThreadLocal

深入研究java.lang.ThreadLocal类 http://lavasoft.blog.51cto.com/62575/51926/

原文: http://my.oschina.net/zouqun/blog/418056
1.官方介绍
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

要点:
1)每个线程都有自己的局部变量
    每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的(有前提,后面解释)

2)独立于变量的初始化副本
    ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。

3)状态与某一个线程相关联
    ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要



从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。




ThreadLocal的API

ThreadLocal的API提供了如下的4个方法,分别是:

T get()
          返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。

protected  T initialValue()
          返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。 如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法
         
          若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

void remove()
          移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

void set(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。


2.应用举例
ThreadLocalTest
package com.meituan;
 
/**
 * Created by zsq on 15/5/20.
 */
public class ThreadLocalTest {
 
    //创建一个Integer型的线程本地变量
    public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int j = 0; j < 5; j++) {
            threads[j] = new Thread(new Runnable() {
                @Override
                public void run() {
                    //获取当前线程的本地变量,然后累加5次
                    int num = local.get();
                    for (int i = 0; i < 5; i++) {
                        num++;
                    }
                    //重新设置累加后的本地变量
                    local.set(num);
                    System.out.println(Thread.currentThread().getName() + " : " + local.get());
 
                }
            }, "Thread-" + j);
        }
 
        for (Thread thread : threads) {
            thread.start();
        }
    }
}


上述程序的运行结果:
Thread-0 : 5
Thread-3 : 5
Thread-2 : 5
Thread-1 : 5
Thread-4 : 5

我们看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。

3.查看源码,深入理解
到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:

set() and get() 
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
 
public T get() {
    //获取当前执行线程
    Thread t = Thread.currentThread();
    //取得当前线程的ThreadLocalMap实例
    ThreadLocalMap map = getMap(t);
    //如果map不为空,说明该线程已经有了一个ThreadLocalMap实例
    if (map != null) {
        //map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量
        ThreadLocalMap.Entry e = map.getEntry(this);
        //如果当前线程本地变量存在这个map中,则返回其对应的值
        if (e != null)
            return (T)e.value;
    }
    //如果map不存在或者map中不存在当前线程本地变量,返回初始值
    return setInitialValue();
}
 
private T setInitialValue() {
    //获取初始化值,initialValue 就是我们之前覆盖的方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //如果map不为空,将初始化值放入到当前线程的ThreadLocalMap对象中
    if (map != null)
        map.set(this, value);
    else
        //当前线程第一次使用本地线程变量,需要对map进行初始化工作
        createMap(t, value);
    //返回初始化值
    return value;
}

ThreadLocal有一个内部类ThreadLocalMap,这个类的实现占了整个ThreadLocal类源码的一多半。这个ThreadLocalMap的作用非常关键,它就是线程真正保存线程自己本地变量的容器。每一个线程都有自己的单独的一个ThreadLocalMap实例,其所有的本地变量都会保存到这一个map中。首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

getMap()
//直接返回线程对象的threadLocals属性
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}  

Thread对象都有一个ThreadLocalMap类型的属性threadLocals,这个属性是专门用于保存自己所有的线程本地变量的。这个属性在线程对象初始化的时候为null。所以对一个线程对象第一次使用线程本地变量的时候,需要对这个threadLocals属性进行初始化操作。注意要区别 “线程第一次使用本地线程变量”和“第一次使用某一个线程本地线程变量”。

4.总结
线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

猜你喜欢

转载自panyongzheng.iteye.com/blog/2213461