ThreadLocal源码探究(Android版)

xiaoxiao2021-02-27  307

Android的Looper实现中核心用到了ThreadLocal这个类,但是基于对这个类不甚了解,所以详细看了源码和参考了一些博客,下面谈下自己的理解

源码参考:Android源码的java.lang.ThreadLocal类(这里边实现同jdk关于ThreadLocal的实现有所区别)

首先可以看下关于ThreadLocal类的官方注释:

/** * Implements a thread-local storage, that is, a variable for which each thread * has its own value. All threads share the same {@code ThreadLocal} object, * but each sees a different value when accessing it, and changes made by one * thread do not affect the other threads. The implementation supports * {@code null} values. * * 实现一个ThreadLocal的容器,这个容器用于提供给每一个线程有针对一个变量有它自己独立的值。 * 所有的线程共享同一个ThreadLocal对象,但是访问这个local对象时有自己单独的值,支持空值 * * @see java.lang.Thread * @author Bob Lee */ public class ThreadLocal<T> { ... }

从这段注释可以看出,ThreadLocal解决的不是多线程共享资源同步的问题,而是多线程访问独立的相同类型的变量的频繁存取和参数传递的复杂性问题

ThreadLocal由于泛型特性,支持读取任意类型的变量ThreadLocal的变量存储 public void set(T value) { //得到当前的线程对象 Thread currentThread = Thread.currentThread(); //得到当前线程的局部变量 Values values = values(currentThread); //如果为空创建一个新的 if (values == null) { values = initializeValues(currentThread); } //存储value到localValues中 values.put(this, value); } Values values(Thread current) { return current.localValues; } Values initializeValues(Thread current) { //每个Thread对象都有一个localValues局部变量 return current.localValues = new Values(); }

通过以上方法实现了用ThreadLocal存储当前线程指定变量的功能

ThreadLocal的的变量读取 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); }

上面的存取方法用到了一个非常重要的类Values,这个类用于线程具体变量的保存,下面重点了解下Values的一些核心方法

Values定义的变量

static class Values { /** * Size must always be a power of 2. * table的初始容量 必须是2的n次方 */ private static final int INITIAL_SIZE = 16; /** * Placeholder for deleted entries. * 被删除的实体 */ private static final Object TOMBSTONE = new Object(); /** * Map entries. Contains alternating keys (ThreadLocal) and values. * The length is always a power of 2. * 存储变量的数组(模拟map),包含ThreadLocal为key和对应的值, * 数组长度总是2的n次方 * table包含key数据类型为null、tombstones、references */ private Object[] table; /** Used to turn hashes into indices. * 计算下标的掩码,它的值是table的长度-1 */ private int mask; /** Number of live entries. * 存放进来的实体的数量 */ private int size; /** Number of tombstones. * 被删除的实体的数量 */ private int tombstones; /** Maximum number of live entries and tombstones. * 用来判断是否需要进行rehash的阈值 */ private int maximumLoad; /** Points to the next cell to clean up. * 下一个要进行清理的位置点 */ private int clean; }

添加数据到Values

/** * Adds an entry during rehashing. Compared to put(), this method * doesn't have to clean up, check for existing entries, account for * tombstones, etc. */ void add(ThreadLocal<?> key, Object value) { //key.hash & mask计算在数组的位置 for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == null) { table[index] = key.reference;//偶数位存储key.reference table[index + 1] = value;//奇数位存储value return; } } }

table被设计为下标为0,2,4…2n的位置存放key(==key的类型可以为null、tombstone、reference==),而1,3,5…(2n +1 )的位置存放value。直接通过下标存取线程变量,它比用WeakReference类在内存占用上更经济,性能也更好。这也是前面中hash的增量要取0x61c88647*2的原因,它也保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位。

存储数据到Values

void put(ThreadLocal<?> key, Object value) { //GC后,处理table中数据,将被GC清理的数据key置为TOMSTONE 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]; //key存在,覆盖key对应的value值 if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } //key不存在 添加key value到当前index if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } //存储key value到第一个要删除的index // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } //当前index有key且与存储key不一致,且当前key为需要被删除的key //则把要存储的key存储到第一个要删的index位置处 // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }

Values类存储key value过程会调用cleanup方法原因在于key为弱引用持有,当进行GC会对弱引用进行回收。处理完GC引起的数据异常后会依次遍历table的index,并在合适的位置进行存储key(ThreadLocal的弱引用)与线程对应变量value到table

cleanUp方法源码

private void cleanUp() { //table扩容 if (rehash()) { // If we rehashed, we needn't clean up (clean up happens as // a side effect). return; } //table还没有存储数据 if (size == 0) { // No live entries == nothing to clean. return; } // Clean log(table.length) entries picking up where we left off // last time. int index = clean; Object[] table = this.table; //遍历table for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) { Object k = table[index]; //key已经被标记或为null if (k == TOMBSTONE || k == null) { continue; // on to next entry } // The table can only contain null, tombstones and references. @SuppressWarnings("unchecked") Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k; //key被GC回收 将key置为TOMBSTONE if (reference.get() == null) { // This thread local was reclaimed by the garbage collector. table[index] = TOMBSTONE; table[index + 1] = null; tombstones++; size--; } } // Point cursor to next index. clean = index; }

单独简单聊下0x61c88647

public class TestHash { private static final int HASH_INCREMENT = 0x61c88647; public static void main(String args[]) { magic_hash(32); } public static void magic_hash(int n) { for (int i = 0; i < n; i++) { int nextHash = i * HASH_INCREMENT + HASH_INCREMENT; System.out.print((nextHash & (n - 1)) + " "); } } } //运行上面的函数会得到类似下面的结果: //7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0

0x61c88647是实现散列常用的值,也是ThreadLocal的变量,用这个计算出来的散列值非常均匀

有可能的内存泄漏

1、ThreadLocal的实例被软引用持有,ThreadLocal使用完置为null没有强引用引用会被GC回收

2、value被当前线程强引用,如果为普通线程,那么任务执行完线程结束 Current Thread, Map, value将全部被GC回收;如果为线程池分配,任务执行完线程会重复使用,此时会导致内存泄漏

总之,每个Thread维护自己的Values类型的局部变量,而Values通过在数组table偶数位存储ThreadLocal为key.reference和奇数位具体变量value的方式完成通过ThreadLocal存取线程变量的过程

转载请注明原文地址: https://www.6miu.com/read-4250.html

最新回复(0)