Java ThreadLocal用法以及源码详解

  • 一、ThreadLocal简介
  • 二、ThreadLocal类图如下
  • 三、ThreadLocal源码解析
  • 四、ThreadLocal的内存泄露
  • 五、附录

本文主要内容为:
1.演示 ThreadLocal 的用法
2.源码解析
3.ThreadLocal内存泄漏

一、ThreadLocal简介

ThreadLocal 类提供了局部的变量,方便同一个 Thread 在多个方法中获取这个变量,其他 Thread 无法获取到这个变量。 如果不使用 ThreadLocal ,也可以将一个上下文对象在多个方法中传递,也可以实现同样的功能,但是使用 ThreadLocal 能降低代码之间的耦合度,实现更加优雅。

可以将 ThreadLocal 理解为一个工具类,最终的 ThreadLocal.set() 的 value 是保存在对应 Thread 的 ThreadLocal.ThreadLocalMap threadLocals 字段中,ThreadLocalMap key 为 ThreadLocal,value 就是 ThreadLocal.set() 的value,同一个 Thread 可以保存多个不同的 ThreadLocal,多个不同的 Thread 也可以保存同一个 ThreadLocal,但是变量值相当于保存在每个 Thread 的本地内存,因而不存在线程安全问题,每个 Thread 操作的都是当前线程的 value,不会影响其他线程。

动动发财小手,关注 + 点赞 + 收藏不迷路。

二、ThreadLocal类图

使用 Intellij Idea 的 PlantUml 插件简单绘制了 ThreadLocal 类图,具体代码详见附录

如下图所示,ThreadLocalMap 是 ThreadLocal 的静态内部类,ThreadLocalMap 的 key 是一个弱引用,调用 ThreadLocal set() 或者 get() 方法,都是操作当前线程的 ThreadLocal.ThreadLocalMap threadLocals 这个map,其中 key 为 ThreadLocal,value 就是 set 的 value。

ThreadLocal类图如下:

在这里插入图片描述

三、ThreadLocal源码解析

ThreadLocal 的 set() 方法代码如下:

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

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

    static class ThreadLocalMap {

        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    // 博主注:该方法最终会调用 expungeStaleEntry(),将entry key = null 对应的 value 也置为 null
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    }

ThreadLocal 的 get() 方法代码如下:

    public T get() {
        Thread t = Thread.currentThread();
        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();
    }

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

    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);
    }

    static class ThreadLocalMap {

        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);
        }

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    // 博主注:该方法会将entry key = null 对应的 value 也置为 null
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
    }

ThreadLocal 的 remove() 方法代码如下:


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

    static class ThreadLocalMap {
        
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    // 博主注:该方法会将entry key = null 对应的 value 也置为 null
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

    }

四、ThreadLocal的内存泄露

通过 ThreadLocal 类图,可以发现 Entry 的key是一个弱引用(WeakReference),当弱引用关联的对象只有弱引用与之关联,当 JVM 进行垃圾回收时,被弱引用关联的对象必定会被回收掉,此时 Entry.get() 就会为 null,但是如果 key 关联的对象是一个强引用,在 JVM 进行垃圾回收的时候,就不会被回收掉。例如弱引用关联强引用对象:ThreadLocal threadLocal = new ThreadLocal(); 只有在执行 threadLocal = null; 之后,Entry.get() 才会为 null。

当我们在线程run() 方法中new 一个 ThreadLocal ,当 run() 方法执行结束,ThreadLocal 生命周期也就结束了,此时 Entry.get() 为 null,但是 value 还是有值的,这就产生了内存泄漏,所以在线程执行完 set() 方法之后,要执行 remove() 方法。

其实在执行 ThreadLocal 的 set()、get()、remove() 方法时,ThreadLocal也会将 Entry key 为 null 对应的 value 置为 null,但是最好还是手动执行 remove() 方法,防止内存泄漏。

五、附录

  1. ThreadLocal类图 plantuml 代码如下:
@startuml

scale 160 width

interface Runnable {
run()
}

class Thread {
~ ThreadLocal.ThreadLocalMap threadLocals
~ ThreadLocal.ThreadLocalMap inheritableThreadLocals
run()
currentThread()
sleep(long millis)
start()
join(long millis)
interrupt()
}

class WeakReference {
}

class Entry {
~ Entry(ThreadLocal k, Object v)
}

class ThreadLocalMap {
- int INITIAL_CAPACITY = 16
- Entry[] table
set(ThreadLocal key, Object value)
getEntry(ThreadLocal key)
remove(ThreadLocal key)
}

class ThreadLocal {
set(T value)
get()
remove()
}

Runnable <|.. Thread : implements
WeakReference <|-- Entry : 继承
Entry <.. ThreadLocalMap : 依赖
ThreadLocalMap <-- Thread : 关联
ThreadLocalMap <.. ThreadLocal : 依赖
Thread <.. ThreadLocal : 依赖


'-			private
'#			protected
'~			package private
'+			public

'parent\n父类 <|-- child\n子类 : extend\n1.继承
'interface\n接口 <|.. achieve\n实现 : implements\n2.实现
'call\n被用方 <.. usage\n使用方 : dependency\n3.依赖
'owner\n主人 --> be_owned\n被拥有 : association\n4.1.关联
'A - B : association both\n4.2.双向关联
'overall\n整体 o--> members\n成员 : aggregation\n5.聚合(单向,关联关系的一种,与关联关系之间的区别是语义上的,关联的两个对象通常是平等的,聚合则一般不平等,有一种整体和局部的感觉,实现上区别不大)
'whole\n总体 *--> part\n部分 : composition\n6.组合
@enduml

引用:
1.https://www.cnblogs.com/fsmly/p/11020641.html
2.https://www.cnblogs.com/xiaozhuanfeng/p/10501093.html

你可能感兴趣的:(java,java,ThreadLocal,内存泄漏)