`
zzu_007
  • 浏览: 23772 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

请您先登录,才能继续操作

Android源码之ThreaLocal

阅读更多
ThreadLocal提供了一种线程操作数据的思想。以前我以为ThreadLocal的实现就是有一个以线程的name为关键字的HashMap,每次请求数据的时候在这个HashMap中查找数据。今天读完代码,我才明白原来是用一种更巧妙的方法实现的。
下面我们就分析一下ThreadLocal的源码实现,
首先我们先看一下ThreadLocal的相关成员变量以及构造函数:

/** Weak reference to this thread local instance. */
    private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);
在这里可以将reference理解为关键字,下面我们可以看到reference的用处

    private static AtomicInteger hashCounter = new AtomicInteger(0);
    private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
hash用于计算hash值,hash值就是为了计算索引地址

构造函数如下:
public ThreadLocal() {}
ThreadLocal的构造函数时一个空函数。

在ThreadLocal类中有一个static的内部类Values. Values类提供了存储和索引具体的值的逻辑
我们也来看一下Values的成员变量以及构造函数:
private static final int INITIAL_SIZE = 16;
初始的存放数据的数组的大小
private static final Object TOMBSTONE = new Object();
TOMBSTONE是一个占位符,表示一个墓碑
private Object[] table;
保存数据的数组
private int mask;
用于计算索引值的掩码
private int size;
size表示有效的数据的个数
private int tombstones;
tombstones译为墓碑,表示索引值的数据无效,但是曾经被使用过的数据的个数
private int maximumLoad;
maximumLoad表示最大的装载数,也就是size+tombstones的最大值
private int clean;
clean记录了下次执行cleanup操作的起始位置

Values只提供了默认构造函数,实现如下:
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
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
}
构造函数只是提供了成员变量的初始化。在实现中我们看到,将mask掩码初始化为table的长度-1, 这样在计算索引值时,保证索引值落在table里面。将maximumLoad初始化为table的长度的2/3.

(1)set函数
接下来我们看一下ThreadLocal的set函数的实现,set函数将值设置到线程中去
public void set(T value) {
//得到当前的运行线程
        Thread currentThread = Thread.currentThread();
//获得运行线程的Values值
        Values values = values(currentThread);
//如果values值为空,则说明还没有为该线程设置过值,这时需要初始化一个空的values对象
        if (values == null) {
            values = initializeValues(currentThread);
        }
//设置值
        values.put(this, value);
    }

values函数的实现如下:
Values values(Thread current) {
        return current.localValues;
    }
我们可以看到,values函数就是返回了线程的localValues成员变量。
在Thread类中,存在ThreadLocal.Values localValues的成员变量,有着包访问权限。从这里我们可以看出,ThreadLocal的值是绑定到各个线程中去的,而不是在ThreadLocal里统一管理。

initializeValues函数的实现也很简单,如下:
Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
只是简单的初始化Thread的localValues成员变量new一个新Values对象。

接下来我们分析一下Values类的put函数的实现:
void put(ThreadLocal<?> key, Object value){
//一上来就执行cleanUp操作
            cleanUp();

//记录第一个墓碑的位置
            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) {
//firstTombstone == -1, 说明在查找过程中没有遇到墓碑,则将该值保存在这个null位置
                    if (firstTombstone == -1) {
                        // Fill in null slot.
//保存key和value
                        table[index] = key.reference;
                        table[index + 1] = value;
//有效的数据个数加1
                        size++;
                        return;
                    }

                    //firstTombstone != -1,说明查找过程中遇到过墓碑,则要将该值替换遇到的第一个墓碑
//保存key和value
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
//墓碑的个数减1
                    tombstones--;
//有效的个数加1
                    size++;
                    return;
                }

                //保存第一个墓碑的位置
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
}
从put的实现逻辑中,我们看到Values是想一个位置保存key,在这个位置的下一个位置处保存value。这就要求table的长度必须为2的整数倍,并且计算索引的步长为2。
我们从next函数的视线中验证了这一点:
private int next(int index) {
return (index + 2) & mask;
}

我们接下来看一下cleanUp函数的实现逻辑:
private void cleanUp() {
if (rehash()) {
//如果重新hash了table,则没有必要在进行cleanUp操作
                return;
            }

//size = 0, 表示还没有保存有效的数据
            if (size == 0) {
                // No live entries == nothing to clean.
                return;
            }

            // 从上次执行cleanUp结束时的索引值开始
            int index = clean;
            Object[] table = this.table;

//遍历table
            for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) {
//得到key
                Object k = table[index];

                if (k == TOMBSTONE || k == null) {
                    continue; // on to next entry
                }


                @SuppressWarnings("unchecked")
                Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
//如果为null,表示此对象已经被回收
                if (reference.get() == null) {
                    //将该位置设置成墓碑,并将值设置为null
                    table[index] = TOMBSTONE;
                    table[index + 1] = null;
//增加墓碑的数目和减少有效的对象数目
                    tombstones++;
                    size--;
                }
            }

            //保存下次cleanUp的其实索引值
            clean = index;
    }
从源码中可以看出,cleanUp主要清理了被垃圾回收器回收的对象。这样能保证每次取数据和设置数据时索引位置的有效性。
我们看一下rehash的函数实现:
private boolean rehash() {
//如果没有达到可装载的最大值,则不用重新hash,直接返回false
            if (tombstones + size < maximumLoad) {
                return false;
            }

            int capacity = table.length >> 1;

            int newCapacity = capacity;

//如果一半的容量都被有效的值所占据,则将容量扩大一倍。因为table是将关键字和值保存在相邻的位置上,所以这里其实比较的是table数组的长度的1/4
            if (size > (capacity >> 1)) {
                newCapacity = capacity * 2;
            }

            Object[] oldTable = this.table;

            //重新分配一个新的table数组
            initializeTable(newCapacity);

       
            this.tombstones = 0;

            //如果有效的值的个数为0, 则没必要进行新旧table的赋值
            if (size == 0) {
                return true;
            }

            //进行新旧table的赋值
            for (int i = oldTable.length - 2; i >= 0; i -= 2) {
                Object k = oldTable[i];
                if (k == null || k == TOMBSTONE) {
                    // Skip this entry.
                    continue;
                }

                // The table can only contain null, tombstones and references.
                @SuppressWarnings("unchecked")
                Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
                ThreadLocal<?> key = reference.get();
                if (key != null) {
                    //该值是有效的,则加入到新的table中
                    add(key, oldTable[i + 1]);
                } else {
                    //该关键字对象已经被回收
                    size--;
                }
            }

            return true;
}
add函数是将值赋值到table数组中。在遍历table的过程中,忽略掉当前索引位置的关键字是墓碑的情况,保证插入到null的位置
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;
}
}
}

(2)get()函数
get()函数负责取出已经保存的值。实现如下:
public T get() {
        //得到当前线程的Values对象
        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成员
            values = initializeValues(currentThread);
        }

//在table里没有查找到
        return (T) values.getAfterMiss(this);
    }

我们看一下getAfterMiss的实现:
Object getAfterMiss(ThreadLocal<?> key) {
//保存当前的数据table
            Object[] table = this.table;
//计算索引值
            int index = key.hash & mask;

            //如果当前关键字为null, 则说明没有保存过该值,则没有继续查找下去的必要
            if (table[index] == null) {
//初始化一个值
                Object value = key.initialValue();

                //如果在initialValue过程中,没有改变table
                if (this.table == table && table[index] == null) {
//将关键字和值 保存
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
//执行一次cleanUp操作
                    cleanUp();
                    return value;
                }

                //在initialValue的过程中,改变了table
//put到table中去
                put(key, value);
                return value;
            }

            int firstTombstone = -1;

            //继续查找
            for (index = next(index);; index = next(index)) {
                Object reference = table[index];
//如果关键字相等
                if (reference == key.reference) {
                    return table[index + 1];
                }

//如果当前索引的关键字为null,则表明当前位置还没有被使用过,则没有必要在查找下去
                if (reference == null) {
//初始化一个值
                    Object value = key.initialValue();

                    //如果在initialValue过程中,没有改变table
                    if (this.table == table) {
//如果我们曾经遇到过墓碑,并且现在那个位置还是个墓碑,则将值赋值到第一个墓碑的位置
                        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;
                        }

//当前索引的位置还是null,则赋值到当前的位置
                        if (table[index] == null) {
                            table[index] = key.reference;
                            table[index + 1] = value;
                            size++;

                            cleanUp();
                            return value;
                        }
                    }

                    //在initialValue的过程中,改变了table
                    put(key, value);
                    return value;
                }

//记录第一个墓碑的位置
                if (firstTombstone == -1 && reference == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
}
在ThreadLocal的实现中,initialValue只是简单的返回一个null,等待子类去实现。
protected T initialValue() {
        return null;
    }
至此,我们分析了set和get函数的实现。
ThreadLocal还实现了remove的逻辑,我们接下来来分析一下remove的实现
(3)remove()函数
remove函数的源码如下:
public void remove() {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            values.remove(this);
        }
    }
得到当前线程对象的values成员之后,调用Values的remove函数即可。Values的remove函数实现如下:
void remove(ThreadLocal<?> key) {
cleanUp();

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

                if (reference == key.reference) {
//如果关键字相等,则将该位置标记为墓碑。
                    table[index] = TOMBSTONE;
                    table[index + 1] = null;
                    tombstones++;
                    size--;
                    return;
                }

                if (reference == null) {
                    // No entry found.
                    return;
                }
}
}

至此,我们完整的分析了ThreadLocal的实现。我们思考一下问题:
为什么要使用一个叫墓碑的占位符呢?
我们知道在table数组中,每一个位置可以存放的对象要么等于null,要么是一个要保存的值。我们只要在按顺序遍历table,总会找到一个位置可以保存数据。如果要删除一个数据,只要将该位置设置为null。这样当查找到一个位置为null的时候,就可以知道这个位置肯定是没有被使用的位置,因此也就没有必要再查找下去了。那为什么还有多引入一个墓碑的对象呢?看似这个墓碑是多余的,其实不然!因为计算每个线程的索引值的时候,每个线程的索引值其实是固定的,每次都会映射到相同的位置。这样有了墓碑这个占位符对象之后,能保证别的对象不会使用这个位置,相当于将这个位置保护起来,以便留给特定的线程去使用。

从ThreadLocal的实现中,我们总结到,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。






分享到:
评论

相关推荐

    Android应用源码45套安卓源码合集.zip

    Android应用源码Android平台下通过HTTP协议实现断点续传下载.rar Android应用源码Hibernate4Android.rar Android应用源码http、udp、tcp网络交互组件.rar Android应用源码ListView实现的目录树结构.rar Android应用...

    Android应用源码11套安卓源码合集.zip

    Android应用源码11套安卓源码合集: Android Gps日志记录程序源码.rar Android listview 滑动删除(具体效果360手机卫士后台通知).rar Android MP3播放器,带卡拉OK字幕.rar Android “遇见”android应用源码.rar ...

    Android应用源码10套安卓源码合集.zip

    android应用源码QQ_UI之分类菜单.rar android应用源码QQ的登录界面.rar android应用源码Socket的客户端通信.rar android应用源码tooxin图信美图.rar android应用源码yannihui(音乐播放器).rar android应用源码Zirco-...

    android 9.0 源码下载(28 r6)

    源码下载对于深入理解Android系统的工作原理、进行定制化开发或优化应用程序至关重要。在本文中,我们将探讨如何下载Android 9.0(28 r6)的源码,以及这个过程涉及的关键知识点。 首先,要下载Android源码,你需要...

    android P android9源码编译.ipr.iml文件

    源码编译对于开发者来说是一项基础且至关重要的工作,它涉及到理解系统内部运作,自定义修改以及构建自己的Android ROM。`.ipr`和`.iml`文件是IntelliJ IDEA(Android Studio是其专为Android开发的分支)项目配置...

    Android源码设计模式解析与实战.PDF(完整版)

    Android源码设计模式解析与实战.PDF(完整版),文件中有百度网盘下载链接。

    Android40个经典源码

    1 Android PDF 阅读器源码 2 Android Txt文本阅读器源码 3 Android SqliteManager 源码 4 Android 个人记账程序源码 5 android 多点触控实例源码 ...40 android源码分享之带手势划动功能的日历源码

    Android 源码源码在线

    Android 源码源码在线访问,覆盖各版本,包括最新的Android L. 无需setup git, 无需费时下载。

    在不同操作系统下编译Android源码需要更改一些Android源码的配置项,脚本用于自动化更改配置项.zip

    在不同操作系统下编译Android源码需要更改一些Android源码的配置项,脚本用于自动化更改配置项.zip 在不同操作系统下编译Android源码需要更改一些Android源码的配置项,脚本用于自动化更改配置项.zip 在不同操作系统...

    android应用源码---浏览器源码(AndroidChromium).rar

    《深入剖析Android Chromium浏览器源码》 Android Chromium是一款开源的浏览器项目,它是Google Chrome浏览器在Android平台上的核心组件。此源码提供了深入了解Android应用程序开发、Web浏览技术以及Chrome浏览器...

    Android 源码 android.iml文件

    Android 源码 android.iml文件,移去了无用的模块方便Android studio快速导入,欢迎下载

    Android源码分析工具及方法

    在分析Android源码的过程中,一些有效的工具和方法可以极大地提高我们的工作效率,确保我们能够快速准确地定位到我们感兴趣的部分。这里,我们主要介绍两种工具:Eclipse和Android Studio IDE,以及Android SDK ...

    wordpress for android源码

    WordPress for Android源码基于Android 4.0(Ice Cream Sandwich)进行开发,这表明项目在设计之初就考虑到了兼容性和性能优化。源码主要分为以下几个关键模块: 1. 数据访问层:这部分代码处理与WordPress REST ...

    android 社区源码(开源中国)

    《深入剖析Android开源中国社区应用源码》 在IT领域,源码分析是提升技能、理解软件设计原理的重要途径。开源中国社区的Android版源码为我们提供了一个宝贵的实践平台,让我们能够深入研究一个完整的社交应用是如何...

    Android源码 炫酷的3D音乐播放器 各种特效OpenGL

    Android源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D...

    50款Android studio项目源码.zip

    《Android Studio项目源码解析与学习指南》 在Android应用开发的世界中,Android Studio作为官方推荐的集成开发环境(IDE),已经成为开发者们的首选工具。本文将深入探讨"50款Android studio项目源码.zip"这一资源...

    android源码大全

    在Android开发领域,深入理解Android源码是提升技术能力的关键步骤。"android源码大全"这个资源包无疑为开发者提供了一个宝贵的自学平台,涵盖了几乎所有的主流Android资源文件和源码。接下来,我们将深入探讨这个...

    android源码下载步骤

    android源码下载步骤

Global site tag (gtag.js) - Google Analytics