深入理解ThreadLocal

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解ThreadLocal相关的知识,希望对你有一定的参考价值。

ThreadLocal是什么:

  • ThreadLocal翻译过来是本地线程,但它却不是线程,只是保存线程的自己使用的变量
  • ThreadLocal是线程封闭的一种实现,什么是线程封闭呢,线程封闭就是将某个对象封闭在一个线程中,使用这种方式将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。假如你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。就不会产生竞态状态了

简单例子

一个实体类,里面有一个Threadlocal类 
只能通过get和set方法去访问该对象的值

public class MyNumber {
    private static ThreadLocal<Integer> number = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        }
    };

    public Integer getNext() {
        number.set(number.get() + 1);
        return number.get();
    }

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        ExecutorService es = Executors.newCachedThreadPool();

        for (int i = 0; i < 2; i++) {
            es.execute(new MyThread(myNumber));
        }

        TimeUnit.SECONDS.sleep(3);
        es.shutdownNow();
    }
}

线程类:MyThread,其里面有一个属性,MyNumber,在run方法里,对LocalThread里保存的值其进行加1操作

public class MyThread implements Runnable {
    private MyNumber myNumber;

    public MyThread(MyNumber myNumber) {
        this.myNumber = myNumber;
    }

    @Override
    public void run() {
        for(int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() 
                    + "-number: " + myNumber.getNext());
        }
    }
}

输出结果:

pool-1-thread-2-number: 1
pool-1-thread-1-number: 1
pool-1-thread-2-number: 2
pool-1-thread-2-number: 3
pool-1-thread-2-number: 4
pool-1-thread-2-number: 5
pool-1-thread-1-number: 2
pool-1-thread-1-number: 3
pool-1-thread-1-number: 4
pool-1-thread-1-number: 5

创建了两个线程,都将调用mynumber的getNext()方法,但两个线程的操作互不影响。 
从输出结果可以看出:确实为每个线程通过ThreadLocal为每个线程创建了一个Integer的存储区域,通过set和get去设置和获得这个值。

ThreadLocal是如何实现线程封闭的

源码中,ThreadLocal是泛型类的,每个线程都将会为这个T类型的值创建存储区域 
构造函数:是一个默认构造函数

 public ThreadLocal() {}

在MyNumber类中,初始化ThreadLocal时重写了initialValue()方法,因为该方法默认返回null

  protected T initialValue() {
        return null;
    }

对于基本类型包装类我们肯定不想返回值是null,所以进行了重写

  • get方法

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

    在获取值的时候,首先要获得当前的线程,以便确定是针对哪个线程获取其中的ThreadLocal保存的值。通过getMap方法获取键值对,获得其中的value值,如果值不等于null,则进行强制转化以后返回,为空,则对其进行初始化一次,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;
        }
    

    其初始化的值仍为initialValue()方法返回的值,这里如果map为空,则会创建一个集合。

getMap方法:

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

Thread类中:

ThreadLocal.ThreadLocalMap threadLocals = null;

说明每一个线程类中都有threadLocals,getMap方法获得当前线程的的threadsLocals的值,是一个ThreadLocalMap类

重点讲解一下ThreadLocal中的Map操作。 
讲Map的时候先讲一下ThreadLocalMap的静态内部类Entry,继承了WeakReference,在无法使用它时,垃圾收集器会将其回收。里面有一个value的属性,用于保存线程的值。

                                                                                             static class Entry extends WeakReference<ThreadLocal<?>> {
               Object value;

                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }

        }

它的super是WeakReference,最终将调用Reference这个类的构造函数。这个类主要是用来为Entry设置一个value值,并调用Refernce构造函数。 
获得Entry的方法也是通过i

    private Entry getEntry(ThreadLocal<?> key) {
       int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
         }

ThreadLocalMap是ThreadLocal的静态内部类 
其中一个构造函数:

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

首先确定了Entry[]数组的大小,通过与运算获得value存储在数组中的第几p的实现方式有点类似 
get方法中首先通过getMap方法获得对应的ThreadLocalMap,并通过getEntry()方法获得Entry对象,从而获得value的值 
set方法

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

set方法里的核心方法应该是ThreadMap类中的set方法

     private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);

                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();

                    if (k == key) {
                        e.value = value;
                        return;
                    }

                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }

                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

该段的意思是首先通过计算hash并做与运算,计算出存储位置,通过存储位置获得value,如果value不为空,则重置value的值,否则table[i]的位置则为Entry 
因为getMap获取的是当前线程的threadLocals,其初始时值为0,所以set时,先调用 createMap(t, value)方法:

void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this,firstValue);
    }

为当前线程的threadLocals进行初始化,传入了一个threadLocal对象和初始化值。

  • 总结 
    ThreadLocal实现为使用相同变量的每个线程创建一个存储空间是通过Thread类的threadLocals实现的。因为每个线程都有一个ThreadLocal.ThreadLocalMap,所以可以做到独立存储。当调用set方法时,首先会获得当前线程类的threadLocals并将其存储到map引用中,第一次调用set时,因为threadLocals初始化值为null,所以先调用creatMap(t,value)方法初始化当前线程的threadLocals(初始化做了什么:通过一个类似于hashMap实现存储的计算获取value在Entry数组中的存储位置),所以当第二次调用set方法时,threadLocals不为空,将调用map.set(this, value)方法,如果通过计算原来Entry[i]上有值,则将替换这上面的值,否则就进行赋值。 
    对于get方法,首先通过获得getMap()方法获得threadLocals的值,一般之前会进行set方法的调用,map不为null,通过getEntry()方法返回保存value的Entry对象,从而获得value值,如果未为进行set方法的调用,则会返回initialValue()方法返回的初始化值。总之,保存和获取都是通过threadLocal实现的。可以将ThreadLocal视为一个Thread-T键值对象,但实际上保存在Thread对象中,当线程终止以后,这些值i将会作为垃圾回收,因为保存value的Entry类继承了WeakReference

以上是关于深入理解ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal深入理解

深入理解ThreadLocal

深入理解 ThreadLocal

对ThreadLocal的深入理解

深入理解ThreadLocal

深入理解ThreadLocal