深入理解ThreadLocal

来源:互联网 发布:王者荣耀数据封神榜67 编辑:程序博客网 时间:2024/06/05 18:48

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: 1pool-1-thread-1-number: 1pool-1-thread-2-number: 2pool-1-thread-2-number: 3pool-1-thread-2-number: 4pool-1-thread-2-number: 5pool-1-thread-1-number: 2pool-1-thread-1-number: 3pool-1-thread-1-number: 4pool-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
原创粉丝点击