1.为啥 说是Android ThreadLocal ,而不是java ThreadLocal,因为Android 对它进行了 优化.优化地方:内存复用,使用弱引用解决内存泄漏.而且他们处理方式也不同Java 使用类来包裹 key和value的.使用魔数0x61c88647, 计算得到的索引值偶数和奇数之间不断切换.而Android 只是在偶数索引index 存放key ,index+1来存放值.魔数为0x61c88647*2 得到的索引值都是偶数,非常适合它的处理方式.

2.从使用来分析源码.

    ThreadLocal local=new ThreadLocal<>();           Object data= local.get();           if (data==null){               data=new Object();            local.set(data);           }               //拿到对象来处理一下问题                    }   

3.ThreadLocal对象的创建:  

   public ThreadLocal() {} 就一个空构造 ,那么创建对象都做了什么呢,那就看它成员属性了.  /** Weak reference to this thread local instance. */    private final Reference> reference            = new WeakReference>(this);//弱引用持有它,有利于回收,防止内存泄漏.如果他为null时它所在的存放数组索引的地方将被设置为TOMBSTONE 对象,value所在的地方设置为null,不在持有它对象也有利于回收.下一个ThreadLocal 对象set时.如果找到数组存放的索引,而且在这个索引数组里面的对象为TOMBSTONE将会被替换成这个.从而达到内存复用. /** Hash counter. */    private static AtomicInteger hashCounter = new AtomicInteger(0);    /**     * Internal hash. We deliberately don't bother with #hashCode().     * Hashes must be even. This ensures that the result of     * (hash & (table.length - 1)) points to a key and not a value.     *     * We increment by Doug Lea's Magic Number(TM) (*2 since keys are in     * every other bucket) to help prevent clustering.     */    private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);为啥使用0x61c88647  防止集中,为啥*2 因为key  所在的索引为偶数.第一次计算时hash 为零.也就是第一次创建ThreadLocal key 必然在0索引.然后系统已经使用了n次(hashCounter 为静态...).

主线程存放的对象

其中Looper最为熟悉.

3.get()方法

   public T get() {        // Optimized for the fast path.        Thread currentThread = Thread.currentThread();//value 是从线程对象 localValues 成员属性取出来的中,所以不同线程有不同value, 就是有不同的副本.        Values values = values(currentThread);        if (values != null) {            Object[] table = values.table;            int index = hash & values.mask;// 尝试从第一次计算hash 得到索引取值,如果key 等于 将执行getAfterMiss 方法.一般都存在第一次计算得到索引的地方            if (this.reference == table[index]) {                return (T) table[index + 1];            }        } else {//初始化数组            values = initializeValues(currentThread);        }        return (T) values.getAfterMiss(this);    } Object getAfterMiss(ThreadLocal<?> key) {            Object[] table = this.table;            int index = key.hash & mask;            // If the first slot is empty, the search is over.            //如果第一次存放的索引都为null ,那么必然没有set过数据            if (table[index] == null) {                Object value = key.initialValue();//默认是null                // If the table is still the same and the slot is still empty...// 看到这里挺懵圈的,为啥这样判断呢.在同一个线程是串行执行的,不应该table 发生变化才对(并发问题)// 直到看 这句话The table changed during initialValue() 就突然明白,如果继承重写initialValue方法在里面set 是不是有可能发生扩容,扩容时索引可能发生偏移.                if (this.table == table && table[index] == null) {                    table[index] = key.reference;                    table[index + 1] = value;                    size++;                    //扩容或者检查key 是否被回收                    cleanUp();                    return value;                }                // The table changed during initialValue().// 当发生扩容时 就要重新遍历索引了                put(key, value);                return value;            }            // Keep track of first tombstone. That's where we want to go back            // and add an entry if necessary.            int firstTombstone = -1;            // Continue search.// 如果不为空 那就继续遍历 而遍历的范围永远都在0-table.length-1 之间,而且必然是偶数            for (index = next(index);; index = next(index)) {                Object reference = table[index];                if (reference == key.reference) {                    return table[index + 1]; //找到就返回                }                // If no entry was found...                // 这里的逻辑跟上面差不多的                if (reference == null) {                    Object value = key.initialValue();                    // If the table is still the same...                    if (this.table == table) {                        // If we passed a tombstone and that slot still                        // contains a tombstone...                        if (firstTombstone > -1                                && table[firstTombstone] == TOMBSTONE) {//这里内存复用                            table[firstTombstone] = key.reference;                            table[firstTombstone + 1] = value;                            tombstones--;                            size++;                            // No need to clean up here. We aren't filling                            // in a null slot.                            return value;                        }                        // If this slot is still empty...                        if (table[index] == null) {                            table[index] = key.reference;                            table[index + 1] = value;                            size++;                            cleanUp();                            return value;                        }                    }                    // The table changed during initialValue().                    put(key, value);                    return value;                }                if (firstTombstone == -1 && reference == TOMBSTONE) {                    // Keep track of this tombstone so we can overwrite it.// 为啥不把  table[firstTombstone] = key.reference...这些语句 放到这里来执行呢.万一后面还有 //reference ==key.reference就不就重复了吗                     firstTombstone = index;                }            }        }

4.cleanUp():扩容或者回收标记(设置TOMBSTONE)

        private void cleanUp() {// 检查是否扩容            if (rehash()) {                // If we rehashed, we needn't clean up (clean up happens as                // a side effect).                return;            }          //数量为0 那就没有必要进行回收标记了            if (size == 0) {                // No live entries == nothing to clean.                return;            }            // Clean log(table.length) entries picking up where we left off            // last time.//这里要从上一次的位置开始检查,为什么呢 因为遍历次数为log2Table.length ,不能完全遍历完,索引需要记录上一次位置才能 完全遍历完.            int index = clean;            Object[] table = this.table;            for (int counter = table.length; counter > 0; counter >>= 1,                    index = next(index)) {                Object k = table[index];                if (k == TOMBSTONE || k == null) { //已经标记跳过                    continue; // on to next entry                }                // The table can only contain null, tombstones and references.                @SuppressWarnings("unchecked")                Reference> reference                        = (Reference>) k;                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;//记录        }              private boolean rehash() {             if (tombstones + size < maximumLoad) {                return false;            }            int capacity = table.length >> 1;                     int newCapacity = capacity;           //当数量大于四分之一时 在扩容两倍 .而当数量大于2分之1 getAndAdd() 才会获得 0索引,后面获取的索引会跟之前一样(测试过).所以1/3 或者1/4 扩容都可以.也就是说在1/3 或者1/4之前获取 索引值是不冲突 ,在set 时用遍历有点想不通.            if (size > (capacity >> 1)) {                // More than 1/2 filled w/ live entries.                // Double size.                newCapacity = capacity * 2;            }            Object[] oldTable = this.table;            // Allocate new table.            initializeTable(newCapacity);            // We won't have any tombstones after this.            this.tombstones = 0;            // If we have no live entries, we can quit here.            if (size == 0) {                return true;            }            // Move over entries.       //既然索引不冲突 向后或者向前遍历都没有关系            for (int i = oldTable.length - 2; i >= 0; i -= 2) {                Object k = oldTable[i];                if (k == null || k == TOMBSTONE) {                    // Skip this entry.                    continue;  //这里回收标记已经不用管了,因为创建了新的数组,所以上面this.tombstones = 0                }                // The table can only contain null, tombstones and references.                @SuppressWarnings("unchecked")                Reference> reference                        = (Reference>) k;                ThreadLocal<?> key = reference.get();                if (key != null) {                    // Entry is still live. Move it over.                    add(key, oldTable[i + 1]); // 找到位置添加进去就行了                } else {                    // The key was reclaimed.                    size--;                }            }            return true;        }

 5.set(...) 方法:

       

 直接贴vaules.put.. void put(ThreadLocal<?> key, Object value) {            cleanUp();//说过                      int firstTombstone = -1;      //getAfter 部分代码差不多 ,唯一比较不理解的是既然索引不冲突 ,是否直接用 key.hash & mask就得了?为啥还遍历?            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;                }            }        }

ps.终于知道为啥要遍历了,因为 static AtomicInteger hashCounter 那么 在另一个线程创建多个Threadlocal 对象,在返回主线程创建就可能出现索引冲突.怪不得自己计算的索引值跟上面的图不一样.

更多相关文章

  1. Android中使用indexablerecyclerview以及右侧字母索引基本使用
  2. Android遍历API (1) 动画篇——克隆动画AnimationCloning
  3. Android-->获取所有联系人以及所有的字段(联系人头像,邮箱,地址,
  4. [android源码下载索引贴】微信+二维码那都不是事......
  5. 获取android联系人信息
  6. Android(安卓)遍历ListView条目控件
  7. 遍历android根目录的简单资源查看器
  8. android分页查询获取系统联系人信息
  9. android触控,先了解MotionEvent

随机推荐

  1. Android面试简录——布局
  2. android视频播放(二) 利用android原生的M
  3. Android(安卓)热修复方案Tinker(一) Appl
  4. Android跨进程通信IPC之7——Binder相关
  5. Android实现头像上传
  6. Android跨进程通信IPC之14——其他IPC方
  7. Android安装、卸载、打开代码
  8. Android(安卓)初体验
  9. android 4.4.2 默认打开wifi
  10. Android跨进程通信IPC之4——AndroidIPC