ThreadLocal源码分析

什么是ThreadLocal

ThreadLocal是一个线程独享的本地存放空间,什么是独享的本地空间,也就是时候每个线程都有一份,
线程之间不能共享该区域。主要用于存储线程变量。

什么情况下会使用ThreadLocal

多线程的情况下,如果存在资源的竞争的时候,我们通常使用锁的机制去处理这种资源的竞争,而对于一些资源
每个线程都要有一份的情况下,我们就会使用ThreadLocal.

例如:

  1. Spring的事务管理,是通过C3P0或者其他的数据库连接池中获取一个Connection,然后将Connection存放每个线程中
    的ThreadLocal中,保证每个线程之间使用的数据库连接都是独享的。我们可以想下,如果每个线程都是使用同一个
    数据连接,我们就无法控制事务的提交和回滚。因此每个线程必须独占这个数据库连接。那如果我们在Dao层
    获取Connection,然后执行SQL提交事务。就不会存在这个问题啦,其实并不是,我们通常会在三层中的服务层进行事务
    的开启和事务的提交、回滚。那么为什么会在服务层进行事务的处理,主要是因为事务的边界问题。
    因为Service会调用一系列的DAO对数据库进行多次操作。多个Dao中我们无法确定到底哪个Dao进行事务的提交。
    因此使用ThreadLocal进行存放每个数据库的连接。
  2. 还有一种场景,在多数据源的情况下,我们可以通过一个key获取对应的数据源,而这个key值
    ,从我们ThreadLocal中获取到对应的数据源。从而隔离多线程下数据源的访问。

ThreadLocal的使用

  • ThreadLocal的使用,我们只需要创建一个ThreadLocal并且指定存放的数据类型即可。
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

  • ThreadLocal类的方法
 public T get() 获取当前线程的变量
 public void set(T value)  设置当前线程的变量
 public void remove() 删除线程变量

ThreadLocal源码分析

一、ThreadLocal结构分析

ThreadLocal的实现原理,我们可以通过ThreadLocal的get方法进行跟踪分析。

1    public T get() {
2        Thread t = Thread.currentThread();
3        ThreadLocalMap map = getMap(t);
4        if (map != null) {
5            ThreadLocalMap.Entry e = map.getEntry(this);
6            if (e != null) {
7                @SuppressWarnings("unchecked")
8                T result = (T)e.value;
9                return result;
10            }
11        }
12        return setInitialValue();
13    }

从ThreadLocal的get方法中我们可以看出,现获取当前的线程,再通过线程去获取
对应线程的ThreadLocalMap.从ThreadLocalMap的名字,我们可以看出他是一个Map的数据结构。
我们就想ThreadLocalMap是存放在哪里的呢?我们可以通过跟中getMap的方法中看出,ThreadLocalMap
是存放到Thread中。

1  ThreadLocalMap getMap(Thread t) {
2        return t.threadLocals;
3    }

而Thread中的ThreadLocalMap是在ThreadLocal中进行初始化。我可以通过ThreadLocal中的set方法和setInitialValue方法查看到初始化ThreadLocalMap.

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
image.png
二、ThreadLocalMap
  • 1.存储结构分析
    我可以从ThreadLocalMap的源码进行查看,ThreadLocalMap跟HashMap等都是使用Entry数据结构进行存储.
1    static class ThreadLocalMap {
2          static class Entry extends WeakReference<ThreadLocal<?>> {
3              /** The value associated with this ThreadLocal. */
4              Object value;
5  
6              Entry(ThreadLocal<?> k, Object v) {
7                  super(k);
8                  value = v;
9              }
10          }
11
12        private Entry[] table;

那么如果我们在代码中创建两个ThreadLocal会怎样存储的呢?

 public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

通过对setInitialValue方法进行分析发现,我们创建两个ThreadLocal,其实内部是将当前的ThreadLocal对象作为键值,initialValue的返回值做为Value。

  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

总结:
我们通过对ThreadLocalMap的set和get方法,可以发现,我们ThreadLocalMap是存Thread中,由ThreadLocal的设置相关方法进行创建。存储的数据结构是以ThreadLocal作为键保存对应的Value到ThreadLocalMap中。


image.png

我们可以通过源码查看到Entry是Key是一个弱引用,而不是像HashMap那样使用强引用。那么ThreadLocalMap中的key为弱引用的,那么ThreadLcaoMap的中的值只能存活到下一次的GC。

弱引用:通常用于存储非必需对象的,弱引用指向的对象实例,在下一次垃圾收集发生之前是存活的。

    1. ThreadLocalMap内存泄漏
      从存储的结构我们可以发现,ThreadLocal是ThreadLocalMap的键值。如果在我们的代码中将 ThreadLocalRef设置为null的情况下,我们就不可以通过ThreadLocal进行访问,而ThreadLocalMap还是会保存Entry,而这样就会造成内存的泄漏。Entry只能等待线程结束后才能进行GC。
import org.junit.Test;


public class UseThreadLocal {
    private static int threadCount = 30;

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            threadLocal2 = null;
            Integer threadLocalInt = threadLocal.get();
            threadLocal.set(threadLocalInt+this.threadId);
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

    }
}

为了防止内存泄漏,我们可以通过ThreadLocal提供的remove移除ThreadlocalMap中ThreadLocal的Value。

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

  • 3.为什么ThreadLocalMap中的ThreadLocal使用弱引用
    上面我们说过当我们为ThreadLocalRef设置为null的时候,ThreadLocal会被回收,而ThreadLocal被调用的前提是没有被其他引用。那么在ThreadLocalMap中会将ThreadLocal作为键值。如果ThreadLocalMap中使用强引用的话,ThreadLocal就一定不会被回收,就会造成ThreadLocal的内存泄漏,但是ThreadLocalMap使用的是弱引用,那也就是说在下一次GC的时候ThreadLocal就会被回收,而不会造成内存泄漏。

    1. ThreadLocalMap内存溢出
      我们使用多线程是为了提高CPU的使用效率或者将一些耗时的工作由其他线程进行处理。
      那么如果我们线程处理的时间非常耗时,存放在ThreadLocal中的数据非常大,并且存放在Threadlocal中的数据没有释放。JVM为了避免内存溢出,
运行时设置堆内存为1m
-Xms=1m
-Xmx=1m


public class ThreadLocalOOM {
    private static int threadCount = 30;

    public static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
        @Override
        protected HashMap initialValue(){
            HashMap map= new HashMap<>();
            for (int i=0;i<1000000000;i++){
                map.put(i,i);
            }
            return map;
        }
    };



    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }
    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 1. 背景 ThreadLocal源码解读,网上面早已经泛滥了,大多比较浅,甚至有的连基本原理都说的很有问题,包括...
    时之令阅读 636评论 1 5
  • 一. 简介 提醒篇幅较大需耐心。 简介来自ThreadLocal类注释 ThreadLocal类提供了线程局部 (...
    BrightLoong阅读 9,193评论 2 14
  • ThreadLocal,线程变量,是一个以ThreadLocal对象为键,任意对象为值 的存储 结构。该结构附着于...
    Justlearn阅读 402评论 0 2
  • 首先通过问题去看源码 ThreadLocal通过空间换取线程变量安全的说法正确吗 ThreadLocal为什么说会...
    Visualing阅读 235评论 0 2
  • 写在前面 本次使用的源码来自于Android API 25 Platform. 引子 在Android中,在子线程...
    houtrry阅读 624评论 0 1