对ThreadLocal的理解

之前在分析Handler的工作原理的时候,在Looper类中有这么一段代码,当时就不怎么理解

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  ...

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

这个ThreadLocal是用来干嘛的呢?下面就来说说我对ThreadLocal的理解

ThreadLocal

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过getset方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。

Handler的运行机制中,必须保证每个线程只有一个Looper对象,Looper类就是利用了ThreadLocal的特性

工作原理

我们来通过源码来分析下ThreadLocal的工作原理,我们先看下ThreadLocal有个内部类Values,它定义了6个成员变量,分别为

 //长度必须是2的n次方的值
 private Object[] table;

//计算下标的掩码, 它的值是table的长度-1,待会会讲到
 private int mask;

 //存放进来的实体的数量
 private int size;

 //被删除的实体的数量
 private int tombstones;

 //一个阈值,用来判断是否需要进行rehash
 private int maximumLoad;

 //下一个要进行清理的位置点
 private int clean;

我们来看一下当Values对象被创建时进行了什么工作

 /**
 * Constructs a new, empty instance.
 */
Values() {
    initializeTable(INITIAL_SIZE);
    this.size = 0;
    this.tombstones = 0;
}

/**
 * Creates a new, empty table with the given capacity.
 */
private void initializeTable(int capacity) {
    this.table = new Object[capacity * 2];
    this.mask = table.length - 1;
    this.clean = 0;
    this.maximumLoad = capacity * 2 / 3; // 2/3
}

上面的代码我们可以看到,当初始化一个Values对象时,它会创建一个长度为capacity*2的数组。

add()

在add()方法当中,也可以看到它会把ThreadLocal对象(key)和对应的value放在连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。直接通过下标存取线程变量, 它比用WeakReference类在内存占用上更经济,性能也更好。它也保证了在计算key的下标时,一定是偶数位。

 void add(ThreadLocal<?> key, Object value) {
    for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];
        if (k == null) {
            table[index] = key.reference;
            table[index + 1] = value;
            return;
        }
    }
}

set()

再来看看set()方法

 public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

//获取当前线程的ThreadLocal
Values values(Thread current) {
    return current.localValues;
}

看看这里的values(currentThread)方法,其实就是取的当前线程的ThreadLocal。因为在Thread类的内容有一个成员专门用于存储线程的ThreadLocal的数据,那就是localValues,在Thread.java

/**
 * Normal thread local values.
 */
ThreadLocal.Values localValues;

put()

接下来看看ThreadLocal的值到底是怎么进行存储的

/**
 * Sets entry for given ThreadLocal to given value, creating an
 * entry if necessary.
 */
void put(ThreadLocal<?> key, Object value) {
    cleanUp();

    // Keep track of first tombstone. That's where we want to go back
    // and add an entry if necessary.
    int firstTombstone = -1;

    for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];

        if (k == key.reference) {
            // Replace existing entry.
            table[index + 1] = value;
            return;
        }

        if (k == null) {
            if (firstTombstone == -1) {
                // Fill in null slot.
                table[index] = key.reference;
                table[index + 1] = value;
                size++;
                return;
            }

            // Go back and replace first tombstone.
            table[firstTombstone] = key.reference;
            table[firstTombstone + 1] = value;
            tombstones--;
            size++;
            return;
        }

        // Remember first tombstone.
        if (firstTombstone == -1 && k == TOMBSTONE) {
            firstTombstone = index;
        }
    }
}

通过上面的存储思路,我们大概可以看出一个规则,那就是ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组的索引为index,那么ThreadLocal的值在table数组中的索引就是index+1。

get()

最后在看一下get()方法,

 public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

很明显,返回的也是table[index + 1]

使用场景

根据ThreadLocal的特性,我们可以大概可以总结它的两个使用场景

  1. 就是通过它在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据, 因为对于其它线程来说无法获取到数据。
  2. 复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂, 这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢? 其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。

##参考
Android的消息机制之ThreadLocal的工作原理
理解Java中的ThreadLocal