并发基础_5_并发_ThreadLocal

来源:互联网 发布:python php 比较 编辑:程序博客网 时间:2024/06/06 13:13

源码下载地址:jdk1.7源码下载


ThreadLocal(线程本地存储)的使用

源码分析对应JDK1.7


ThreadLocal是什么?
注意:ThreadLocal与线程同步无关,并不是为了解决多线程共享变量问题!

ThreadLocal源码中的解释:


大概意思是:
ThreadLocal类用来提供线程内部的局部变量。
这些变量在多线程环境下访问(通过get或set)时,能保证各个线程里的变量相对于独立于其他线程内的变量
ThreadLocal实例通常来说是 private static类型;


小结:
ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。

应用场景:
ThreadLocal的主要应用场景为:按线程多实例(每个线程对应一个实例)对象的访问,并且这个对象很多地方都要用到。

例如:网站用户登录,每个用户服务端都会为其开一个线程,每个线程中创建一个ThreadLocal,里面存了用户的基本信息,
在很多页面跳转的时候,会显示用户信息或者得到用户的其他数据等频繁操作,

这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据


来看一个Demo

public class SequenceNumber {private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {// 通过匿名内部类覆盖ThreadLocal的initalValue()方法,指定初始值。@Overrideprotected Integer initialValue() {return 0;}};// 从ThreadLocal修饰的对象中,获取下一个值。public int getNextNum() {seqNum.set(seqNum.get() + 1);return seqNum.get();}}public class TestThread extends Thread {// 被ThreadLocal修饰的对象的类?(这样说合适吗?)private SequenceNumber sequenceNumber;public TestThread(SequenceNumber sequenceNumber) {this.sequenceNumber = sequenceNumber;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + " - " + sequenceNumber.getNextNum());}}}public class TestMain {public static void main(String[] args) {SequenceNumber number = new SequenceNumber();TestThread t1 = new TestThread(number);TestThread t2 = new TestThread(number);TestThread t3 = new TestThread(number);t1.start();t2.start();t3.start();}}输出结果:Thread-0 - 1Thread-0 - 2Thread-0 - 3Thread-1 - 1Thread-1 - 2Thread-1 - 3Thread-2 - 1Thread-2 - 2Thread-2 - 3
从输出结果看,三个线程所输出的序列号,都来自同一个SequenceNumber实例,
但是他们并没有发生互相干扰的情况,而是各自输出序列号。

这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。


ThreadLocal源码解析

ThreadLocal可以看作为一个容器,容器里面存放着属于当前线程的变量。

ThreadLocal类提供了四个对外的接口,也是操作ThreadLocal类的基本方法:

protected T initialValue() :返回此线程局部变量的当前线程的“初始值”。此方法是protected方法,显然是为了让子类覆盖而设计的。此方法是一个延迟方法,在线程第一次调用get()时才执行,并且执行一次,默认返回nullpublic void remove() :移除此线程局部变量当前线程的值。目的是减少内存的占用,该方法是JDK5新增的方法需要说明的是,当线程结束之后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程局部变量并不是必须的操作,但它可以加快内存回收的速度public void set(T value) :将此线程局部变量的当前线程副本中的值设置为指定值。public void remove() :移除此线程局部变量当前线程的值。

通过以上四个方法实现了ThreadLocal中变量的访问,设置数据,初始化以及删除局部变量,

那ThreadLocal内部是如何为每一个线程维护变量副本的呢?

其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(类似于Map),用键值对的形式存储每一个线程的变量副本。
ThreadLocalMap中元素的key为ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal

ThreadLocal源码:

每一个Thread内,都会有一个ThreadLocalMap用来存放自己用到的ThreadLocalkeyThreadLocal实例,valueset进去的值

/*** Returns the value in the current thread's copy of this thread-local variable. * If the variable has no value for the current thread, it is* first initialized to the value returned by an invocation of the* {@link #initialValue} method.** @return the current thread's value of this thread-local*/public T get() {// 当前线程Thread t = Thread.currentThread();// 获取当前线程对应的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 获取对应ThreadLocal的变量值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T) e.value;}// 若当前线程还未创建ThreadLocalMap,// 则返回调用setInitialValue方法并在其中调用createMap方法进行创建并返回初始值。return setInitialValue();}/*** Variant of set() to establish initialValue. Used instead of set() in case* user has overridden the set() method.** @return the initial value*/private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}/*** Sets the current thread's copy of this thread-local variable to the* specified value. Most subclasses will have no need to override this* method, relying solely on the {@link #initialValue} method to set the* values of thread-locals.** @param value*            the value to be stored in the current thread's copy of this*            thread-local.*/// 设置变量的值public void set(T value) {// 获取到当前线程Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)// key为ThreadLocal对象,value为set进来的对象map.set(this, value);elsecreateMap(t, value);}/*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t*            the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t*            the current thread* @param firstValue*            value for the initial entry of the map* @param map*            the map to store.*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}/*** Removes the current thread's value for this thread-local variable. If* this thread-local variable is subsequently {@linkplain #get read} by the* current thread, its value will be reinitialized by invoking its* {@link #initialValue} method, unless its value is {@linkplain #set set}* by the current thread in the interim. This may result in multiple* invocations of the <tt>initialValue</tt> method in the current thread.** @since 1.5*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}

我们来看ThreadLocal的set()方法,乍一看是通过当前线程Thread获取的了ThreadLocalMap,
其实是从Thread中提取到了ThreadLocalMap对象,我们看下getMap(t)方法,
ThreadLocal.ThreadLocalMap threadLocals = null;
这就是ThreadLocal为什么是每个线程单独拥有的。

在每个Thread中会有一个ThreadLocalMap,当我们调用某一个ThreadLocal实例的set()方法时,
会去当前的Thread中获取ThreadLocalMap,使用ThreadLocal实例为key,将set进来的值作为value存储在map中。


上面的源码是ThreadLocal类中的几个主要方法,其核心都是对ThreadLocalMap进行操作,下面看看ThreadLocalMap的源码:

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = v;}}/** * The initial capacity -- MUST be a power of two. */private static final int INITIAL_CAPACITY = 16;/** * The table, resized as necessary. table.length MUST always be a power * of two. */private Entry[] table;/** * The number of entries in the table. */private int size = 0;//....其他的方法操作和Map类似}
为不同线程创建不同的ThreadLocalMap,并用线程本身作为区分点,每个线程之间其实没有任何的联系,
说是说存放了变量的副本,不过可以理解为为每个线程单独new了一个对象。

两个问题:
a. 每个线程的变量副本是存储在哪里的?
b. 变量副本是怎么从共享变量赋值出来的?源码中ThreadLocal的初始值是什么时机设置的?

第一个问题的本质其实是:ThreadLocal是怎么实现了多线程之间,每个线程一个变量副本?它是如何实现的?

分析:
ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量。
可以从ThreadLocal的get函数中看出,其中getMap函数是用t作为参数的,这里的t就是当前执行的线程对象。

ThreadLocalMap map = getMap(t);
在getMap(t)方法中,只有一段代码

return t.threadLocals;
再深入看下,t.threadLocals返回的是什么?

ThreadLocal.ThreadLocalMap threadLocals = null;
这个threadLocals是Thread类中的,也就是说从Thread对象中获取了ThreadLocalMap。

从上面的分析得知get()方法就是从当前线程的threadLocalMap中取出当前线程对应的变量副本。
注意:变量是保存在线程中的,而不是保存在ThreadLocal变量中。

当前线程中,有一个变量引用名字是threadLocals,这个引用是在ThreadLocal类中createmap函数内初始化的。
每个线程都有一个这样的threadLocals引用的ThreadLocalMap。

这样,我们所使用的ThreadLocal变量的实际数据,通过get()方法取值的时候,就是通过取出Thread中的threadLocals引用的Map,然后从这个map中根据当前threadLocal作为参数,取出数据。

我们来看下initialValue()方法的官方说明:

Returns the current thread's "initial value" for this thread-local variable. This method will be invoked the first time a thread accesses the variable with the get method, unless the thread previously invoked the set method, in which case the initialValue method will not be invoked for the thread. Normally, this method is invoked at most once per thread, but it may be invoked again in case of subsequent invocations of remove followed by get. This implementation simply returns null; if the programmer desires thread-local variables to have an initial value other than null, ThreadLocal must be subclassed, and this method overridden. Typically, an anonymous inner class will be used.

大致意思是:
返回此线程局部变量的当前线程的“初始值”。 
该方法将在线程首次使用get方法访问变量时被调用除非线程先前调用了set方法,在这种情况下,将不会为线程调用initialValue方法。 
通常,每个线程最多调用此方法一次,但是如果随后调用remove,则可以再次调用此方法。
这个实现只是返回null; 
如果程序员希望线程局部变量具有除null之外的初始值,则ThreadLocal必须被子类化,并且该方法被覆盖。
通常,将使用匿名内部类。

通过上面的分析,解决了第一个问题。


第二个问题分析:
变量副本(每个线程中保存的那个ThreadLocalMap中的变量)是怎么声明和初始化的?

博主原文中的答案用set()方法举例子,不过看了源码,以及自己的Demo,觉得应该先从get()方法去分析。个人见解,如有错误,还请指正。

首先看get()方法

public T get() {// 当前线程Thread t = Thread.currentThread();// 获取当前线程对应的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 获取对应ThreadLocal的变量值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T) e.value;}// 若当前线程还未创建ThreadLocalMap,// 则返回调用setInitialValue方法并在其中调用createMap方法进行创建并返回初始值。return setInitialValue();}
当我们第一次调用get()方法
a. 获取当前线程对象
b. 然后获取当前线程对象的ThreadLocalMap(第一次调用,获取的map为null)
c. 然后调用setInitialValue()方法

我们接下去看setInitialValue()方法:
private T setInitialValue() {// 这个initialValue()方法,我们在内部类中已经重写// 通过上面对initialValue()方法官方说明,// 这里我认为是调用重写内部类中的初始值(就是Demo中的number = 0;)T value = initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取Thread线程对象中的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// (第一次调用map为null)if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
a. 通过阅读initialValue()方法官方解释,我认为这里是调用重写内部类中的初始值(就是Demo中的number = 0;)
b. 获取当前线程对象
c. 从当前线程对象中获取ThreadLocalMap(第一次调用获取map为null)
d. 调用createMap()方法

继续看createMap(t, value);方法

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
创建了一个ThreadLocalMap,将ThreadLocal对象和value存入ThreadLocalMap中。

所以,ThreadLocal第一次调用get()方法(不调用set()方法),也能获取到值,前提是重写initialValue()方法并赋值。

下面我们看set()方法:

// 设置变量的值public void set(T value) {// 获取到当前线程Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)// key为ThreadLocal对象,value为set进来的对象map.set(this, value);elsecreateMap(t, value);}
捋顺了get()方法,看set()方法是不是感觉很轻松,不重复说了,对照get()方法看吧。

说明:
1. 在代码中声明的ThreadLocal对象,实际上只有一个。

2. 在每个线程中,都维护了一个threadLocals对象,在没有ThreadLocal变量的时候threadLocals为null。
一旦在ThreadLocal的createMap函数中初始化之后,这个threadlocals就初始化了


还有一个问题:
如果在代码中,定义了多个线程局部变量ThreadLocal对象,那么在Thread中的threadlocals是怎么存储的呢?

其实不论在get()方法还是set()方法中,都能看到这样一段代码:

map.set(this, value);
这里的map是ThreadLocalMap,也就是threadlocals,在set的时候,将ThreadLocal对象,作为key存入了map中。


参考资料:
http://www.iteye.com/topic/1123824
http://blog.csdn.net/lovesomnus/article/details/64441426
http://www.importnew.com/20963.html

原创粉丝点击