zoukankan      html  css  js  c++  java
  • ThreadLocal

    ThreadLocal是什么呢?

    其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。

    线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,

    是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地拥有和改变自己的副本,而不会和其它线程的副本冲突。

    从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;
    在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
    通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,
    从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

    案例

     1     private static final ThreadLocal threadSession = new ThreadLocal(); 
     2   public static Session getSession() throws InfrastructureException { 
     3     Session s = (Session) threadSession.get(); 
     4     try { 
     5       if (s == null) { 
     6         s = getSessionFactory().openSession(); 
     7         threadSession.set(s); 
     8       } 
     9      } catch (HibernateException ex) { 
    10            throw new InfrastructureException(ex); 
    11     } 
    12      return s; 
    13 }

    可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,
    如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,
    实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),
    而threadSession作为这个map的key,要取得这个session可以通过threadSession.get()来得到,
    里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。
    显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。


    试想如果不用ThreadLocal怎么来实现呢?

    可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。
    或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,
    但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,
    这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。

    原理:

    首先看Thread里面的代码:

    1 public class Thread implements Runnable {  
    2     ......  
    3   
    4     /* ThreadLocal values pertaining to this thread. This map is maintained 
    5      * by the ThreadLocal class. */  
    6     ThreadLocal.ThreadLocalMap threadLocals = null;    
    7     ......  
    8 } 

    这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中。

    看ThreadLocal:

     1 下面来看看ThreadLocal的实现原理(jdk1.5源码) 
     2 public class ThreadLocal<T> {
     3 
     4     private final int threadLocalHashCode = nextHashCode();
     5     private static int nextHashCode = 0;
     6     private static final int HASH_INCREMENT = 0x61c88647;
     7 
     8     private static synchronized int nextHashCode() {
     9         int h = nextHashCode;
    10         nextHashCode = h + HASH_INCREMENT;
    11         return h;
    12     }
    13 
    14     public ThreadLocal() {
    15     }
    16 
    17     public T get() {
    18         Thread t = Thread.currentThread();
    19         ThreadLocalMap map = getMap(t);
    20         if (map != null)
    21             return (T)map.get(this);
    22 
    23         // Maps are constructed lazily.  if the map for this thread
    24         // doesn't exist, create it, with this ThreadLocal and its
    25         // initial value as its only entry.
    26         T value = initialValue();
    27         createMap(t, value);
    28         return value;
    29     }
    30 
    31 
    32     public void set(T value) {
    33         Thread t = Thread.currentThread();
    34         ThreadLocalMap map = getMap(t);
    35         if (map != null)
    36             map.set(this, value);
    37         else
    38             createMap(t, value);
    39     }
    40 
    41 
    42     ThreadLocalMap getMap(Thread t) {
    43         return t.threadLocals;
    44     }
    45 
    46     void createMap(Thread t, T firstValue) {
    47         t.threadLocals = new ThreadLocalMap(this, firstValue);
    48     }
    49 
    50     .......
    51 
    52     static class ThreadLocalMap {
    53 private Entry[] table;
    54 static class Entry extends WeakReference<ThreadLocal> { 55 /** The value associated with this ThreadLocal. */ 56 Object value; 57 58 Entry(ThreadLocal k, Object v) { 59 super(k); 60 value = v; 61 } 62 } 63 64 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { 65 table = new Entry[INITIAL_CAPACITY]; 66 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 67 table[i] = new Entry(firstKey, firstValue); 68 size = 1; 69 setThreshold(INITIAL_CAPACITY); 70 } 71 // ... ... 72 } 73 74 }

    可以看到ThreadLocal类中的变量只有这3个int型:

    1 private final int threadLocalHashCode = nextHashCode(); 
    2 private static int nextHashCode = 0; 
    3 private static final int HASH_INCREMENT = 0x61c88647;

    而作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,
    实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,
    而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。

    threadLocalHashCode的作用可以类比HashMap,用来唯一区分ThreadLocal,每一个ThreadLocal的实例,这个值都是不相同的

    根据这个值可以从thradlocalmap中的entry数组中存取对应的值。

    可以来看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,
    从上面看到构造函数ThreadLocal()里什么操作都没有,唯一的操作是这句:
    private final int threadLocalHashCode = nextHashCode();

    那么nextHashCode()做了什么呢:

    1 private static synchronized int nextHashCode() { 
    2   int h = nextHashCode; 
    3   nextHashCode = h + HASH_INCREMENT; 
    4   return h; 
    5 } 

    就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。
    因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,
    那么ThreadLocal.set()进去的对象是放在哪儿的呢?

    看一下上面的set()方法,两句合并一下成为
    ThreadLocalMap map = Thread.currentThread().threadLocals;

    这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:
    public class Thread implements Runnable {
    ......
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ......
    }

    再看这句:
    if (map != null)
    map.set(this, value);

    也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中

    ThreadLocal的threadLocakhashcode用来确定在map中的entry数组中的位置 从而方便快速的读和设置值

    每当new一个ThreadLocal实例 他的threadLocalHashcode的值都是不同的,从而2个threadlocal不会产生哈希冲突。

    ThreadLocal的方法:

    1 get

    1     public T get() {  
    2         Thread t = Thread.currentThread();  
    3         ThreadLocalMap map = getMap(t);  
    4         if (map != null)  
    5             return (T)map.get(this);    
    6         T value = initialValue();  
    7         createMap(t, value);  
    8         return value;  
    9     }
    42     ThreadLocalMap getMap(Thread t) {
    43         return t.threadLocals;
    44     }

    1 获取当前的thread实例 Thread t = Thread.currentThread();
    2 获取当前thread实例持有的map ThreadLocalMap map = getMap(t);
    3 判断该map是否为空 if (map != null)
        1 not null : 按照key(this)值获取value return (T)map.get(this);
        2 null :
          1 生成一个初始化的值 T value = initialValue();
          2 为当前线程创建一个map createMap(t, value);
             void createMap(Thread t, T firstValue) {
                t.threadLocals = new ThreadLocalMap(this, firstValue);
            }
         3 返回初始化值 return value;

    可以看出该map以this(当前threadlocal对象)做key值   所以一个threadlocal实例只能对应一个值

    每当new threadlocal实例后 与他相关的threadLocalHashCode的值就会发生改变 所以多个threadlocal的threadLocalHashCode的不同

    所以通过产生多个threadlocal实例  来为线程保存不同类型的变量

    一个Thread中才有一个map实例,用它来存放多个threadlocal和object的键值对
    那么当前thread就会保有多个不同类型的变量值

    当线程销毁时相应的东西也一起销毁了

    2protected T initialValue()

    返回此线程局部变量的当前线程的初始值。
    最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。
    如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
    在程序中一般都重写initialValue方法,以给定一个特定的初始值。

    3 void remove()

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

    4 set

    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     } 

    1 获取当前的thread实例 Thread t = Thread.currentThread();
    2 获取当前thread实例持有的hashmap ThreadLocalMap map = getMap(t);
    3 判断该hashmap是否为空 if (map != null)
        1 not null : 以this为key设置值 map.set(this, value);
        2 null :
           1 生成一个初始化的值 T value = initialValue();
           2 为当前线程创建一个map createMap(t, value);

     1  1        private void set(ThreadLocal key, Object value) {
     2  2 
     3  3             Entry[] tab = table;
     4  4             int len = tab.length;
     5  5             int i = key.threadLocalHashCode & (len-1);//根据threadLocalHashCode确定在数组中的位置
     6  6 
     7  7             for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) {
     8  8                 ThreadLocal k = e.get();//获取该位置的的key
     9  9 
    10 10                 if (k == key) {//2个key相同 值替换
    11 11                     e.value = value;
    12 12                     return;
    13 13                 }
    14 14 
    15 15                 if (k == null) {
    16 16                     replaceStaleEntry(key, value, i);
    17 17                     return;
    18 18                 }
    19 19             }
    20 20 
    21 21             tab[i] = new Entry(key, value);//该位置没有key表明还没有插入一个值 插入该key-value
    22 22            int sz = ++size; 
    23 23        if (!cleanSomeSlots(i, sz) && sz >= threshold) 
    24 24         rehash(); 
    25 25 }

    小结:

    ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。
    ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存
    单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
    ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
    因为每个线程拥有一个副本 也就没有了安全性问题 (用空间内存来换取的)
    ThreadLocal的使用比synchronized要简单得多。

    ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。
    synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
    其他线程必须排队等待 (以时间来换取的)

    ThreadLocal并不能替代synchronized,它们处理不同的问题域。
    Synchronized用于线程间的数据共享,实现同步机制,比ThreadLocal更加复杂。 保证多并发时数据的安全性
    而ThreadLocal则用于线程间的数据隔离

  • 相关阅读:
    八款前端开发人员更轻松的实用在线工具
    HTML5中的Web Notification桌面通知(右下角提示)
    老司机程序员用到的各种网站整理
    JAVA变量存储
    关于JAVA中的前期绑定 后期绑定(动态绑定)
    i MySQL 查看约束,添加约束,删除约束
    final static
    MySQL alter语句
    MySQL 权限生效
    MySQL 用户权限管理
  • 原文地址:https://www.cnblogs.com/wihainan/p/4774385.html
Copyright © 2011-2022 走看看