ThreadLocal 源码解析和使用

来源:互联网 发布:淘宝签单时间怎么算 编辑:程序博客网 时间:2024/06/05 03:43

ThreadLocal定义

ThreadLocal是Java语言提供用于支持线程局部变量的类。
ThreadLocal不是为了解决多线程访问共享变量,而是通过为每个线程提供一个独立的变量副本来解决变量并发访问的冲突问题。

ThreadLocal常用的4个方法

在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal

public void set(T value)设置当前线程的线程局部变量的值
public T get()返回当前线程所对应的线程局部变量
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法
protected T initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

ThreadLocal使用

用Andorid中的ThreadLocal来举例,因为Android中的源码比较容易理解
定义一个Demo1类

public class Demo1 {    ThreadLocal<People> local = new ThreadLocal<People>(){        @Override        protected People initialValue() {//默认返回一个People对象            return new People();        }    };    /**     * 设置值     */    public void setName(String name) {        People people = local.get();        people.name = name;    }    /**     * 获取值     */    public String getName() {        People people = local.get();        return people.name;    }}

定义一个People类

public class People {    public String name = "zhongguo";}

在MainActivity中添加按钮,添加点击时间,打印log

private void btn1Click() {        Demo1 demo1 = new Demo1();        String name = demo1.getName();        Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);        demo1.setName("xiaoMing");        new Thread(new Runnable() {            @Override            public void run() {                Demo1 demo1 = new Demo1();                String name = demo1.getName();                Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);                demo1.setName("xiaoHong");                //睡2秒                SystemClock.sleep(2000);                name = demo1.getName();                Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);            }        }).start();        //睡2秒        SystemClock.sleep(2000);        name = demo1.getName();        Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);    }

打印log:

10-17 14:15:17.227 9397-9397/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[main,5,main]name:zhongguo10-17 14:15:17.229 9397-9487/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[Thread-7976,5,main]name:zhongguo10-17 14:15:19.229 9397-9397/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[main,5,main]name:xiaoMing10-17 14:15:19.231 9397-9487/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[Thread-7976,5,main]name:xiaoHong

以上是ThreadLocal的简单使用,并不难,下面通过源码来进一步了解ThreadLocal

ThreadLocal源码分析

ThreadLocal的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();    }

第2行获取到当前执行线程对象的引用
第3行获取ThreadLocalMap对象,稍后解析
第4-11行如果map不为空,则再获取ThreadLocalMap.Entry,如果e不为空则拿到e中的value值并返回,稍后解析
第12行获取初始化值,当map为null或者内部e为空就会走此方法

getMap(t)方法

ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }class Thread implements Runnable {        ....省略....        //这个对象的是在ThreadLocal中被实例出来的        ThreadLocal.ThreadLocalMap threadLocals = null;        ....省略....   }

可知threadLocals是在Thread中定义,但实例的是在ThreadLocal初始化的

提前看下ThreadLocal中的createMap方法

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

threadLocals对象就是在这个方法中初始化的,并且每个线程只会创建一个ThreadLocalMap对象。稍后再看这个对象都做了什么?

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方法

protected T initialValue() {        return null;    }

这个方法我们可以重写,默认返回为null,在上边的例子中我们重写并返回一个People对象
当map不为空时,就保存到ThreadLocalMap中,以当前的ThreadLocalMap对象为key
当map为空时,调用了crateMap方法,就是创建ThreadLocalMap对象,并把value值保存到ThreadLocalMap中

createMap方法

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

因createMap方法只会调用一次,所以每个线程只会创建一个ThreadLocalMap对象

ThreadLocalMap静态内部类

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {        Object value;        Entry(ThreadLocal<?> k, Object v) {            super(k);            value = v;        }    }    //初始容量,必须是2的幂    private static final int INITIAL_CAPACITY = 16;    //entry数组,用于放entry对象的,数组长度必须是2的幂    private Entry[] table;    //默认值    private int threshold; // Default to 0    //设置调整Entry数组大小阈值  超过2/3时要扩容    private void setThreshold(int len) {        threshold = len * 2 / 3;    }    //构造函数    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是ThreadLocal类中的静态内部类,实例却被Thread类持有,相当于每个线程只持有一个map

ThreadLocalMap构造函数

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

第2行创建一个大小为16的Entry数组
第3行由当前传进来的ThreadLocal对象和其他值算出来一个索引值
第4行把创建出来的Entry对象保存到索引值的位置上
第5行根据size的值判断Entry数组实际使用了多少
第6行设置超过容量的2/3时,可扩容

说明:这里只是初始化了大小为16的Entry数组,并没有给每个角标赋Entry对象。而是一个线程中有多少个局部变量需要保存, 就初始化多少个Entry对象来保存它们。但每个ThreadLocal对象只能存储一种类型的变量或者一个对象,那初始化大小为16的数组有什么用呢, 这其实就是为了满足我们存储多种类型变量或者对象用的,只不过我们需要创建多个ThreadLocal对象。

Entry构造函数

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

Entry对象是用于保存ThreadLocal对象和value的实体,模拟了map的存储方式ThreadLocal<\?>为key,Object为value,其实就是对象中存储了两个值, 一个ThreadLocal对象,一个ThreadLocal对象对象的Object。ThreadLocal对象获取方式是通过Reference对象中的get方法获取的, 因Entry对象把Object值设置成了成员变量,获取方式可直接new Entry().value。

因Entry继承WeakReference,所以在Entry构造中通过super(k)将ThreadLocal对象变成一个弱引用的对象,便于内存不足时及时回收。

ThreadLocalMap set方法

private void set(ThreadLocal<?> key, Object value) {            Entry[] tab = table;            int len = tab.length;            //根据ThreadLocal对象计算出索引值            int i = key.threadLocalHashCode & (len-1);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                 //能进来,说明Entry肯定不为空                ThreadLocal<?> k = e.get();                if (k == key) {                    e.value = value;//直接返回值                    return;                }                if (k == null) {//说明被回收了,                    //因Entry没有被回收,所以可复用                    replaceStaleEntry(key, value, i);                    return;                }            }            //保存新的Entry对象到Entry数组中            tab[i] = new Entry(key, value);            //便于判断Entry数组是否需要扩容            int sz = ++size;            //重新做一遍清理,并判断是否需要扩容            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

set方法总结

  1. 根据ThreadLocal对象计算出索引值
  2. 根据索引值获取到Entry对象,若不为空,则复用Entry对象,若为空,则创建新Entry对象
  3. 重新清理一遍Entry数组,若需要扩容则扩容

ThreadLocalMap getEntry方法

private Entry getEntry(ThreadLocal<?> key) {            //根据ThreadLocal对象计算出索引值            int i = key.threadLocalHashCode & (table.length - 1);            //获取Entry对象            Entry e = table[i];            //都为true则返回Entry            if (e != null && e.get() == key)                return e;            else                //说明Entry对象或Entry对象的key已经被回收                //需要对Entry对象进行回收,否则该value不清理会内存泄露                return getEntryAfterMiss(key, i, e);        }

ThreadLocalMap getEntryAfterMiss方法

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {            Entry[] tab = table;            int len = tab.length;            //如果e==null说明已经被回收,则直接返回null            //如果e!=null,则先获取ThreadLocal对象                //如果ThreadLocal对象和传进了的key相等则返回Entry对象                //如果k=null则说明已经被回收,则需要对Entry对象进行回收                //因expungeStaleEntry对entry进行了回收,下次循环时e==null了,退出循环,返回null            while (e != null) {                ThreadLocal<?> k = e.get();                if (k == key)                    return e;                if (k == null)                    expungeStaleEntry(i);                else                    i = nextIndex(i, len);                e = tab[i];            }            return null;        }

ThreadLocalMap expungeStaleEntry方法

private int expungeStaleEntry(int staleSlot) {            Entry[] tab = table;            int len = tab.length;            //Entry对象中的value设null,Entry对象设置null,Entry数组使用率减1            tab[staleSlot].value = null;            tab[staleSlot] = null;            size--;            // Rehash until we encounter null           //清理完了之后再继续循环一遍,并重新将entry hash一遍。           Entry e;            Entry e;            int i;            for (i = nextIndex(staleSlot, len);                 (e = tab[i]) != null;                 i = nextIndex(i, len)) {                ThreadLocal<?> k = e.get();                if (k == null) {                    e.value = null;                    tab[i] = null;                    size--;                } else {                    int h = k.threadLocalHashCode & (len - 1);                    if (h != i) {                        tab[i] = null;                        // Unlike Knuth 6.4 Algorithm R, we must scan until                        // null because multiple entries could have been stale.                        while (tab[h] != null)                            h = nextIndex(h, len);                        tab[h] = e;                    }                }            }            return i;        }

getEntry方法总结

  1. 根据ThreadLocal对象计算出索引值,拿到Entry对象
  2. 如果Entry不为空,Entry.get() == key则直接返回Entry对象
  3. 如果Entry为空,说明Entry对象已经被回收,返回null,如果Entry.get() == null,说明Entry对象中key已经被回收,需要对value进行清理否则会内存泄露

ThreadLocal总结

  1. 为每个线程创建唯一的ThreadLocalMap对象,在对象中初始化大小为16的Entry数组,为每个线程保存独立的变量或对象副本
  2. 设置Entry类继承WeakReference并把ThreadLocal对象传递给WeakReference,使ThreadLocal变成弱引用对象,以便于Entry和ThreadLocal对象都易于回收
  3. 在Entry中存储的ThreadLocal对象如果被回收了,ThreadLocal会通过expungStaleEntry方法来清理value值和Entry对象以防止出现内存泄漏
原创粉丝点击