JDK源码学习03-ThreadLocal源码简析

JDK源码学习03-ThreadLocal源码简析

在读完源码之后值得注意的细节:

  1. 在get方法中,先获取map,如果map不为空才就从map中取,如果map为空,就返回initialValue()
  2. 每个线程使用完ThreadLocal之后,需要调用ThreadLocal的remove方法

ThreadLocal简介

多个线程可能都需要某些相同的属性,我们可以在集成Thead类或者实现Runnable的时候,给类赋予私有属性,但是这样做比较呆板,于是ThreadLocal就出现了。比如在Java的数据库连接池中,多个线程会被分别分配一个connection对象,而一个线程必然从始至终都是只使用一个connection对象的(我们可以想想为什么?比如两个线程使用一个connection对象对数据库进行事务操作,就乱套了)。此时我们希望可以给每个线程设置自己的专属属性,就使用ThreadLocal来实现。

但需要注意的是,ThreadLocal会出现内存泄漏的情况,下面也会详细结合JVM的GC讲解。

ThreadLocal其实原理很简单,主要就是在Thread类中本来就拥有一个属性,这个属性就专属于这个线程。学习Java时间久了一点,发现Java确实一切皆对象,Class是对象,Void是对象,线程Thread肯定也是对象。
既然ThreadLocalMap是每个线程都有的私有对象,说明该对象不存在线程竞争问题,并且生命周期从创建(使用ThreadLocal的set或者withInitial会初始化这个map对象),如果不手动删除的话,会一直到线程结束才停止。
比如:

我们把每个线程想象成一个人,而

十个人竞争一个共享单车,这是不安全的(并且每个人没有自己的独特性)。但是线程的属性 ThreadLocal.ThreadLocalMap threadLocals = null;的出现相当于给每个人都配备了一个停车位,可以允许每个人拥有自行车。而ThreadLocal的静态方法withInitial则实现了给threadLocals赋值初始值,相当于给每个人的车位都配一辆基础版自行车,这样每个人用自己的自行车就可以了。并且每个人都可以对自己的自行车进行改装,并不会影响别人的自行车。就算没有使用静态方法withInitial,就算大家都没有被分配基础版自行车,但还是有停车位,可以通过ThreadLocal的set方法,给自己购买一辆自行车。

 ThreadLocal.ThreadLocalMap threadLocals = null;
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

看下面的代码,我们创建了一个ThreadLocal threadLocal对象,并且被两个线程先后访问。第一个线程修改了threadLocal的值,第二个线程再次访问的时候,发现获得的还是初始值。这是因为ThreadLocal给每个线程都初始化了一个ThreadLocalMap,里面的key是ThreadLocal对象,而value是学生对象,所以尽管线程A先于线程B访问并修改这个对象,但其实修改的是线程ThreadA.ThreadLocalMap中的kv对threadLocal->student中的student对象的值。而线程二读取的是ThreadB.ThreadLocalMap中的kv对threadLocal->student中的student对象的值。

	static ThreadLocal<Student> threadLocal = ThreadLocal.withInitial(()->new Student("minxu"));
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread()+"开始干活。。。");
            Student student = threadLocal.get();
            student.setName("heiheihei");
            System.out.println(student);
        },"A").start();
        new Thread(()->{
            sleep(1000);
            System.out.println(   Thread.currentThread()+"开始干活。。。");
            Student student = threadLocal.get();
            System.out.println(Thread.currentThread()+" "+student);
        },"B").start();
        sleep(1500);
    }
    public static void sleep(int time){
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

输出:
Thread[A,5,main]开始干活。。。
Student{name=‘heiheihei’}
Thread[B,5,main]开始干活。。。
Thread[B,5,main] Student{name=‘minxu’}




下面我们来讲讲实现的过程和代码

然后以ThreadLocal作为key,将数据存储进入每个线程中,这样就不存在进程关系。

在这里插入图片描述

简单使用实例

源码查看

核心代码很简单,只有下面两三个函数
有手就能看懂,,,

	// 本函数保证了只初始化一次和懒加载
	public T get() {
        Thread t = Thread.currentThread();
        //ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
	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;
    }

ThreadLocal内存泄漏原理

如果是强引用(实际是弱引用) --ThreadLocalMap中的key指向了ThreadLocal对象

我们知道JVM的gc只会对一个跟不可达的对象进行一次fanalize检查一次gc回收。

   当线程Thread运行的时候,线程的属性ThreadLocal.ThreadLocalMap threadLocals是不会回收的,
不仅ThreadLocal的引用指向了ThreadLocal对象,而ThreadLocalMap中的key指向了ThreadLocal对象,
(我们可以先假设key-》ThreadLocal是强引用,为了改进此问题,实际使用的是弱引用)。所以即使ThreadLocal使用完毕
即ThreadLocal的引用不再指向了ThreadLocal对象,,由于还有key指向ThreadLocal对象,也没有办法回收ThreadLocal,
只有等Thread运行完才能回收。Thread的生命周期是很长的,所以这就叫做内存泄漏,无用对象无法回收。

在这里插入图片描述

实际是弱引用 –还是存在问题

    我们将ThreadLocalMap中的key指向了ThreadLocal对象的引用改成弱引用
这样在栈中的强引用挂了之后,gc扫描到ThreadLocal对象就会把ThreadLocal对象回收了

这样看起来是不是没问题了?但是我们忽略了一件事,就是key虽然被回收了,但是value指向的对象没有被回收!!!! 也就是说ThreadLocalMap中的value如果不手动删除 如果线程生命很长,就会产生内存泄漏问题。
在这里插入图片描述


版权声明:本文为qq_45117655原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。