前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal浅析

ThreadLocal浅析

原创
作者头像
笔头
修改2022-02-14 15:18:44
3840
修改2022-02-14 15:18:44
举报
文章被收录于专栏:Android记忆Android记忆

深入了解ThreadLocal的话,可以看下 深入细节ThreadLocalMap

一、ThreadLocal是什么

ThreadLocal是一个工具,可以操作当前线程的ThreadLocalMap数据,ThreadLocalMap数据不能跨线程,为解决多线程程序的并发问题提供了一种新的思路。

二、ThreadLocal使用

有时我们不想当前线程中的值被其他线程修改。下面示例可能不能准确地表达思想,误喷啊。。

代码语言:javascript
复制
public int TestValue=0;
public static void main(String[] args) {
    Test test=new Test();
    System.out.println("当前线程 值"+test.TestValue);
    test.updateValue();
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("当前线程 值"+test.TestValue);
}
代码语言:javascript
复制
public void updateValue(){
    Thread T1=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新线程 修改前 值"+TestValue);
            TestValue=1;
            System.out.println("新线程 修改后 值:"+TestValue);
        }
    });
    T1.start();
}

我们可以得到以下输出

我们可以看到新线程修改TestValue值后,当前线程受影响了。

如果我们用ThreadLocal变量呢,这样的行为会不会影响我们程序使用?我们来看下

代码语言:javascript
复制
public ThreadLocal<Integer> TestLocalValue2=new ThreadLocal<>();
public static void main(String[] args) {
    Test test=new Test();
    test.TestLocalValue2.set(0);
    System.out.println("当前线程 值"+test.TestLocalValue2.get());
    test.updateThreadLocalValue();
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("当前线程 值"+test.TestLocalValue2.get());
}

public void updateThreadLocalValue(){
    Thread T1=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新线程 修改前 值"+TestLocalValue2.get());
            TestLocalValue2.set(1);
            System.out.println("新线程 修改后 值"+TestLocalValue2.get());
        }
    });
    T1.start();
}

我们可以得到以下输出

我们可以发现,

1.新线程修改ThreadLocal变量数据,对当前线程不受影响

2.新线程拿不到当前线程ThreadLocal变量数据,新线程修改ThreadLocal变量前,拿到的数据是null。

三、源码粗略说明

我们来看下get方法里面做了什么事

代码语言:javascript
复制
public T get() {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t);//获取该线程中ThreadLocalMap数据
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//获取当前线程ThreadLocal为key的Entry数据
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//创建ThreadLocalMap同时返回null
}
代码语言:javascript
复制
ThreadLocalMap getMap(Thread t) { //t 是当前线程
    return t.threadLocals; //默认初始值是null
}
代码语言:javascript
复制
private T setInitialValue() {
    T value = initialValue(); // 默认初始值null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t); 
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); //创建 ThreadLocalMap
    return value;  //返回null
}
代码语言:javascript
复制
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

代码语言:javascript
复制
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

get方法大致流程是

1.获取当前线程,找到当前线程ThreadLocalMap数据

2.如果ThreadLocalMap不为null,且有数据,则返回数据。

3.如果ThreadLocalMap不为null,但是没有数据,则把null添加到ThreadLocalMap中,同时返回null。

4.如果ThreadLocalMap为null,则创建ThreadLocalMap,同时返回null。

我们再来看下set方法

代码语言:javascript
复制
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

大致流程是

1.获取当前线程,找到当前线程ThreadLocalMap数据。

2.如果ThreadLocalMap不为null,则把数据添加到ThreadLocalMap中。

3.如果ThreadLocalMap为null,则创建ThreadLocalMap。

我们可以看到ThreadLocal里面核心是ThreadLocalMap。ThreadLocal数据的操作底层是由ThreadLocalMap完成的。我们来看看ThreadLocalMap结构

代码语言:javascript
复制

private static final int INITIAL_CAPACITY = 16;// 初始容量 —— 必须是2的冥

private Entry[] table;//存放数据的table

private int size = 0;// 数组里面entrys的个数,可以用于判断table当前使用量是否超过负因子

private int threshold; // Default to 0  进行扩容的阈值,表使用量大于它的时候进行扩容

Entry存储结构

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁,导致ThreadLocal对象无法被回收的问题。

四、简单总结

整个流程不复杂。里面主要涉及到三个主体,Thread、TheadLocalMap、TheadLocal。我们画个关系图来看下

TheadLocal中的get、set方法实际是调用当前线程Thread中TheadLocalMap的get、set方法,对Entry数组进行各种操作。同时TheadLocal是Entry中的key。

以上是对TheadLocal简单了解,对TheadLocal深入了解,可以看下 深入细节ThreadLocalMap

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、ThreadLocal是什么
  • 二、ThreadLocal使用
  • 三、源码粗略说明
  • 四、简单总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档