深入理解ThreadLocal

来源:互联网 发布:android 监听网络断开 编辑:程序博客网 时间:2024/05/30 23:55
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
 
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
 
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

API说明
ThreadLocal()
          创建一个线程本地变量。
 
T get()
          返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。 
protected  T initialValue()
          返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
 
   若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
 
void remove()
          移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
 
void set(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
 
在程序中一般都重写initialValue方法,以给定一个特定的初始值。
 

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

应用实例:

1.

package com.liuhui.thread;public class ThreadLocalDemo {private static ThreadLocal<Integer>t=new ThreadLocal<Integer>();public static int  test(){    Integer num=t.get();if(num==null){    num=10;    t.set(num);    }    return num;}public static void main(String[] args) {    System.out.println(test());}}

2.

package com.liuhui.thread;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public class TestConn {private static ThreadLocal<Connection>t=new ThreadLocal<Connection>();public static Connection  test() throws SQLException {    Connection conn=t.get();    if(conn==null){conn=DriverManager.getConnection("","","");t.set(conn);    }    return conn;}public static void main(String[] args) throws Exception {    System.out.println(test());}}


3.

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.func.axc.threadlocal;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. public class ThreadLocalTest {  
  7.       
  8.     //创建一个Integer型的线程本地变量  
  9.      static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {  
  10.         @Override  
  11.         protected Integer initialValue() {  
  12.             return 0;  
  13.         }  
  14.     };  
  15.       
  16.     static class Task implements Runnable{  
  17.         private int num;  
  18.           
  19.         public Task(int num) {  
  20.             this.num = num;  
  21.         }  
  22.   
  23.         @Override  
  24.         public void run() {  
  25.             //获取当前线程的本地变量,然后累加10次  
  26.             Integer i = local.get();  
  27.             while(++i<10);  
  28.             System.out.println("Task " + num + "local num resutl is " + i);  
  29.         }  
  30.     }  
  31.       
  32.     static void Test1(){  
  33.         System.out.println("main thread begin");  
  34.         ExecutorService executors = Executors.newCachedThreadPool();  
  35.         for(int i =1;i<=5;i++) {  
  36.             executors.execute(new Task(i));  
  37.         }  
  38.         executors.shutdown();  
  39.         System.out.println("main thread end");  
  40.     }  
  41.       
  42.     public static void main(String[] args){  
  43.         Test1();  
  44.     }  
  45.    
  46. }  
输出结果:

可以看到各个线程之间的变量是独门的,不会相影响。


通过上面的一个实例,简单的了解了ThreadLocal的用法,下面再来看下其源码实现。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

下面分析其源码

首先是其包含的方法:


它的构造函数不做什么:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ThreadLocal() {  
  2. }  

其实主要的也就下面几个方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public T get() { }  
  2. public void set(T value) { }  
  3. public void remove() { }  
  4. protected T initialValue() { }  
(1)get

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public T get() {  
  2.     Thread t = Thread.currentThread();  
  3.     ThreadLocalMap map = getMap(t);  
  4.     if (map != null) {  
  5.         ThreadLocalMap.Entry e = map.getEntry(this);  
  6.         if (e != null) {  
  7.             @SuppressWarnings("unchecked")  
  8.             T result = (T)e.value;  
  9.             return result;  
  10.         }  
  11.     }  
  12.     return setInitialValue();  
  13. }  

这个上方法就是用来取得变量的副本的,注意到它先取得了当前线程对象,接下来使用了getMap返回一个ThreadLocalMap

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ThreadLocalMap getMap(Thread t) {  
  2.     return t.threadLocals;  
  3. }  

然后可以知道ThreadLocalMap这个竟然是从线程中取到的,好,再打开线程类看看

发现Thread类中有这样一个变量:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ThreadLocal.ThreadLocalMap threadLocals = null;  
也变是说每一个线程都有自己一个ThreadLocalMap。

在我们第一次调用get()函数 时,getMap函数返回的是一个null的map.接着就调用setInitialValue()

看看setInitialValue,它才是真正去初始化map的地方!

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private T setInitialValue() {  
  2.     T value = initialValue();  
  3.     Thread t = Thread.currentThread();  
  4.     ThreadLocalMap map = getMap(t);  
  5.     if (map != null)  
  6.         map.set(this, value);  
  7.     else  
  8.         createMap(t, value);  
  9.     return value;  
  10. }  

其中initialValue这个方法就是我们要重写的,一般我们在这里通过一个new 方法返回一个新的变量实例

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. protected T initialValue() {  
  2.     return null;  
  3. }  

因为是第一次调用get(),所以getMap后的map还是为null。这时就调用到createMap

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void createMap(Thread t, T firstValue) {  
  2.     t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3. }  
终于创建ThreadLocalMap!
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
  2.     table = new Entry[INITIAL_CAPACITY];  
  3.     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
  4.     table[i] = new Entry(firstKey, firstValue);  
  5.     size = 1;  
  6.     setThreshold(INITIAL_CAPACITY);  
  7. }  

这里就将Thread和我们的ThreadLocal通过一个map关联起来。意思是每个Thread中都有一个ThreadLocal.ThreadLocalMap。其中Key为ThreadLocal这个实例,value为每次initialValue()得到的变量!

接下来如果我们第二次调用get()函数,这里就会进入if方法中去!

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public T get() {  
  2.     Thread t = Thread.currentThread();  
  3.     ThreadLocalMap map = getMap(t);  
  4.     if (map != null) {  
  5.         ThreadLocalMap.Entry e = map.getEntry(this);  
  6.         if (e != null) {  
  7.             @SuppressWarnings("unchecked")  
  8.             T result = (T)e.value;  
  9.             return result;  
  10.         }  
  11.     }  
  12.     return setInitialValue();  
  13. }  
进入If方法中后。就会根据当前的thradLocal实例为Key,取得thread中对应map的vale.其中getEntry方法只是取得我们的key-value对。注意,这时的table其实就是在ThreadLocal实例中都会记录着每个和它关联的Thread类中的ThreadLocalMap变量

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private Entry getEntry(ThreadLocal<?> key) {  
  2.     int i = key.threadLocalHashCode & (table.length - 1);  
  3.     Entry e = table[i];  
  4.     if (e != null && e.get() == key)  
  5.         return e;  
  6.     else  
  7.         return getEntryAfterMiss(key, i, e);  
  8. }  
是取得我们的key-value对之后就可取value了,然后就是返回result.如果这时取不到entry,那么又会调用到setInitalValue()方法,过程又和上面的一样了。这里就不说了!

(2)set

这个方法就是重新设置每一个线程的本地ThreadLocal变量的值

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void set(T value) {  
  2.     Thread t = Thread.currentThread();  
  3.     ThreadLocalMap map = getMap(t);  
  4.     if (map != null)  
  5.         map.set(this, value);  
  6.     else  
  7.         createMap(t, value);  
  8. }  
这里取得当前线程,然后根据当前的thradLocal实例取得其map。然后重新设置 map.set(this, value);这时这个线程的thradLocal里的变量副本就被重新设置值了!

(3)remove

就是清空ThreadLocalMap里的value,这样一来。下次再调用get时又会调用 到initialValue这个方法返回设置的初始值

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void remove() {  
  2.     ThreadLocalMap m = getMap(Thread.currentThread());  
  3.     if (m != null)  
  4.         m.remove(this);  
  5. }  

总结:

1、每个线程都有自己的局部变量
每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的(有前提,后面解释)
2、独立于变量的初始化副本
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。
3、状态与某一个线程相关联
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要。



参考:http://blog.csdn.net/evankaka/article/details/51705661
0 0