一、前言
之前看 Android Looper 源码时,在看到 Looper.prepare 时,有个静态成员变量:sThreadLocal:
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));
}
我们看到,先 get ,没有就会去 set。
然后就很好奇的去看了下 ThreadLocal 源码,看源码的过程中,JDK又给了我一个惊喜:
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
发现了一个很奇怪的常数,但不知道这个常数是怎么得来的,故在网上查找了相关资料,总算明白这个常数的含义,以及 ThreadLocal 的作用。
二、ThreadLocal
先来说说这个 ThreadLocal 是什么吧?
ThreadLocal 是线程的内部存储类,可以在指定的线程内,存储数据,同时,也只有指定的线程可以访问该数据;不同的数程是无法访问其它线程的这个变量的,因此,也就做到了线程的数据隔离。
This class provides thread-local variables. These variables differ from their normal counterparts in
that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently
initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in
classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
官方注释:ThreadLocal 提供了线程本地变量,和其它的变量不同,只有每个线程自己可以 get / set,每个变量(副本)是独立的。
2.1、ThreadLocal.set
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // map的key = ThreadLocal, value 是要存储的对象
else
createMap(t, value); // 没有就为这个 Thread 创建 ThreadLocalMap 并赋值
}
ThreadLocalMap getMap(Thread t) {
// Thread 中有个 threadLocals变量
// 类型:ThreadLocal.ThreadLocalMap
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
2.2、ThreadLocal.get
public class ThreadLocal<T> {
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(); // 初始值为 null
}
}
2.3、ThreadLocalMap
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始容量,之后扩容是 2的N次方扩容
private static final int INITIAL_CAPACITY = 16;
// 存储 key & value
private Entry[] table;
// 已使用的个数
private int size = 0;
// 当前 table.length * 2 / 3,即超过三分之二就开始扩容,和 HashMap的负载因子 0.75 不一样
private int threshold;
}
因为 ThreadLocalMap 的生命周期与 Thread 一样长(Thread 持有 threadLocals),当你的 key( ThreadLocal )为 null 时,value 仍被 table 持有,因此,需要主动调用 ThreadLocal 的 remove 方法来清除内存。
三、魔数:0x61c88647
我们在一进入 ThreadLocal 类时,就能看到一段代码:
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode(); // 1640531527
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647; // 1640531527
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
上面的代码注释中也解释到:HASH_INCREMENT 是为了让哈希码能均匀的分布在2的N次方的数组里。
每个 ThreadLocal 都使用该散列值!
散列是什么?
散列算法
- 取模法
- 平方散列法(平方取中法)
- 斐波那契(Fibonacci)散列法
- 随机数法
- 链地址法(拉链法)
可以看出用斐波那契散列法调整之后会比原来的除法散列离散度好很多。
再来说说,魔数怎么来的?
public class HashTest {
public static void main(String[] args) {
long lg = (long) ((1L << 32) * (Math.sqrt(5) - 1)/2);
System.out.println("as 32 bit unsigned: " + lg);
int i = (int) lg;
System.out.println("as 32 bit signed: " + i);
System.out.println("MAGIC = " + 0x61c88647);
}
}
// as 32 bit unsigned: 2654435769
// as 32 bit signed: -1640531527
// MAGIC = 1640531527
可以发现 0x61c88647 与一个神奇的数字产生了关系,它就是 (Math.sqrt(5) - 1)/2。
也就是传说中的黄金比例 0.618(0.618 只是一个粗略值),即 0x61c88647 = 2^32 * 黄金分割比。同时也对应上了上文所提到的斐波那契散列法。
四、ThreadLocal 应用场景
ThreadLocal的使用场景:
- 某些对象在多线程并发访问时可能出现问题,比如使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了,我们就可以用ThreadLocal<SimpleDataFormat>来解决并发修改的问题;
- 另一种场景是Spring事务,事务是和线程绑定起来的,Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离:
public abstract class TransactionSynchronizationManager {
//线程绑定的资源,比如DataSourceTransactionManager绑定是的某个数据源的一个Connection,在整个事务执行过程中
//都使用同一个Jdbc Connection
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
//事务注册的事务同步器
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
//事务名称
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
//事务只读属性
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
//事务隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
//事务同步开启
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}
}
但是不是说一遇到并发场景就用ThreadLocal来解决,我们还可以用synchronized或者锁来实现线程安全。