zoukankan      html  css  js  c++  java
  • 浅析ThreadLocal

      这是我的第一篇博客,条理不是很清晰,不过还是希望能对大家有所帮助。

      首先明确一下这个类的作用,ThreadLocal类是用来为每个线程提供了一份变量的副本,即每个线程的局部变量。每个线程都在自己的栈空间里存有这个变量的值(对象的话就是引用),它们之间是线程隔离的,一个线程对这个ThreadLocal变量做的修改不会被其他线程看见。

    下面先看一个例子。

     1 import java.util.Random;
     2 
     3 public class ThreadLocalTest {
     4     private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
     5         protected Integer initialValue() {
     6             return 6;
     7         }
     8     };
     9     
    10     public static int get(){
    11         return threadId.get();
    12     }
    13     
    14     public static void main(String[] args) {
    15         new Thread(new MThread("线程A")).start();
    16         new Thread(new MThread("线程B")).start();
    17         new Thread(new MThread("线程C")).start();        
    18     }
    19     
    20     static class MThread implements Runnable{
    21         private String name;
    22         public MThread(String str){
    23             name = str;
    24         }
    25         public void run() {
    26             int id = get();
    27             System.out.println("thread "+ name +"'s Id : "+ id);
    28             threadId.set(10 * new Random().nextInt(10));
    29             for(int i = 0; i<3; i++){
    30                 id = get();
    31                 System.out.println("**thread "+ name +"'s Id : "+ id);
    32             }
    33         }
    34     }
    35 }

    结果:

    thread 线程B's Id : 6
    **thread 线程B's Id : 60
    **thread 线程B's Id : 60
    **thread 线程B's Id : 60
    thread 线程A's Id : 6
    **thread 线程A's Id : 50
    **thread 线程A's Id : 50
    **thread 线程A's Id : 50
    thread 线程C's Id : 6
    **thread 线程C's Id : 90
    **thread 线程C's Id : 90
    **thread 线程C's Id : 90

      从上面的结果中我们可以看出每个线程一开始调用get()得到的值都是6,而从输出顺序可知,线程A第一次调用get()时线程B已经在28行set()过threadId,但是A得到的还是6,说明B线程的改动对A是不可见的。

      然后我们看看源码中的注释:

    大意是说每个线程都有一份自己的,独立于别人初始化的变量副本。下面就用源码中的example来看看是不是独立初始化的。

     1 import java.util.concurrent.atomic.AtomicInteger;
     2 
     3 public class ThreadLocalId {
     4     private static final AtomicInteger nextId = new AtomicInteger(0);
     5     private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
     6         protected Integer initialValue() {
     7             return nextId.getAndIncrement();
     8         }
     9     };
    10     
    11     public static int get(){
    12         return threadId.get();
    13     }
    14     
    15     public static void main(String[] args) {
    16         new Thread(new MThread("线程A")).start();
    17         new Thread(new MThread("线程B")).start();
    18         new Thread(new MThread("线程C")).start();        
    19     }
    20     
    21     static class MThread implements Runnable{
    22         private String name;
    23         public MThread(String str){
    24             name = str;
    25         }
    26         public void run() {
    27             int id ;
    28             for(int i = 0; i<3; i++){
    29                 id = get();
    30                 System.out.println("thread "+ name +"'s Id : "+ id);
    31             }
    32         }
    33     }
    34 }

    结果:

    thread 线程A's Id : 1
    thread 线程A's Id : 1
    thread 线程C's Id : 2
    thread 线程C's Id : 2
    thread 线程B's Id : 0
    thread 线程B's Id : 0
    thread 线程B's Id : 0
    thread 线程A's Id : 1
    thread 线程C's Id : 2

     从结果中可以看出每个线程的值都是不同的,这就是因为它们是独立初始化的。因为它们第一次调用get()时如果之前没有set()过值或remove()过,则会调用initialValue()方法来初始化值,而这个方法在第6行中进行了覆盖,每次都是返回nextId的值,而nextId每次取完值都会自增,所以每个线程得到的值都会不同。上面的循环3次是为了说明源码注释中的这句:remains unchanged on subsequent calls,后续调用会返回不变的值(当然前提是期间没有通过set()改变过值。。)。

    这从get()的源码中可以看出:

    public T get() {
      Thread t = Thread.currentThread();
      ThreadLocalMap map
    = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }

    首先会得到当前调用get()方法的线程对象,然后通过getMap()方法获取到与这个线程绑定的ThreadLocalMap对象,下面是getMap()的实现

    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

     很简单,就是获取Thread类的成员变量 ThreadLocal.ThreadLocalMap threadLocals = null; 在Thread类中默认为null,接上面的,当Map为空时,就调用setInitialValue();

    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;
        }

    而在setInitialValue()中,调用了覆盖后的initialValue()取得一个值给value,刚刚是因为Map为null而调的这个方法,所以就会执行 createMap(t, value);而这个方法就是new一个ThreadLocalMap对象赋值给参数t线程,构造参数第一个this是Threadlocal对象,用来在Entry中对应着一个value。

    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, 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中持有一个Entry的数组,而每一个Entry里就有一个ThreadLocal对应着一个value值,再结合get()的源码就可以知道大体思路就是:threadLocal变量的值的获取是首先找到当前线程对象,然后在取得这个线程对象的成员变量threadLocalMap,然后再通过threadLocalMap中的Entry[]取得这个当前ThreadLocal对应的Entry对象,然后Entry.value就得到了最终的值。至于怎么找到当前Thread对应的Entry对象就是下面的代码:

     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         }
     8 
     9 private Entry getEntry(ThreadLocal key) {
    10             int i = key.threadLocalHashCode & (table.length - 1);
    11             Entry e = table[i];
    12             if (e != null && e.get() == key)
    13                 return e;
    14             else
    15                 return getEntryAfterMiss(key, i, e);
    16         }

    在第4行放入的时候就是通过ThreadLocal的成员threadLocalHashCode按位与上Entry数组初始化容量-1获得的下标,所以getEntry()时,第10行先得出下标i,再table[i]就能获取得到Entry了。

    最后用自己的话说就是:每个线程都有一个ThreaLocalMap变量,这个Map里面有一个Entry数组,里面保存了很多个ThreadLocal和Value的映射,当你调用get()时,发现线程没有ThreadLocalMap就创建一个ThreadLocalMap,发现Entry数组里没有找到这ThreadLocal对应的Entry就创建一个Entry,在把值(这个值的获取就是在那个initialValue里面)放进去就好了,最后返回这个值。由此可知每个线程都是有用一块空间来保存这个变量的,所以是线程隔离的,互不干扰的。

    至于set()方法其实是类似的,看源码就能知道了。

    remove()方法提供主动删除不再使用的threadLocal对应的Entry,这样能避免出现内存泄漏。

    在getEntry()中也有调用getEntryAfterMiss()来保障能清理掉stale(陈旧)的Entry,能在很大程度上解决内存泄漏问题。

    这里说只要线程还存活,都有持有一个隐式的引用到threadLocal变量,这个引用链为:thread->threadLocalMap->Entry->threadLocal,当线程结束后,其所有的线程局部实例的副本都会被GC回收(除非还存在其他的引用到这些副本)。

      非常感谢你能看到最后,如果有什么意见或建议,欢迎大家指出,我会积极改进的。

    如有错误,欢迎大家指正,共同进步。
  • 相关阅读:
    preg_match正则匹配的字符串长度问题
    jquery获得select option的值 和对select option的操作
    一笔一划画蜻蜓
    PHPMyadmin 配置文件详解(配置)
    用smarty产生随机数
    svn提交后测试网站自动发布的配置
    linux打包压缩命令汇总
    HR 业务相关
    ABAP中TYPES与DATA、TYPE与LIKE 区别
    HRinfotype增强笔记(转)
  • 原文地址:https://www.cnblogs.com/deng720/p/6278562.html
Copyright © 2011-2022 走看看