Thread和ThreadLocal、ThreadLocalMap的关系

ThreadLocal是什么

 ThreadLocal官方注释:

 翻译过来大致意思是:ThreadLocal可以提供局部变量,通过set和get方法对局部变量进行操作,并且局部变量是每个线程独立的、数据隔离的。ThreadLocal通常作为线程的私有的静态变量,用于和UserId、事务Id相关联。

set方法:

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();

        //获取ThreadLocalMap
        ThreadLocal.ThreadLocalMap map = getMap(t);
        //如果ThreadLocalMap不是空则直接把当前ThreadLocal作为key存到map中
        if (map != null)
            map.set(this, value);
        else
            //如果ThreadLocalMap是空,就初始化map
            createMap(t, value);
    }

getMap方法:

    //获取ThreadLocalMap就是获取当前线程的threadLocals属性
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

createMap方法:

    void createMap(Thread t, T firstValue) {
        //新增一个ThreadLocalMap,把当前ThreadLocal作为key存到map中
        //并且把新增的ThreadLocalMap作为当前线程的threadLocals属性
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

get方法:

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取ThreadLocalMap
        ThreadLocal.ThreadLocalMap map = getMap(t);
        //如果ThreadLocalMap不为空就通过当前ThreadLocal对象获取Entry,返回Entry的值
        if (map != null) {
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T) e.value;
                return result;
            }
        }
        //如果ThreadLocalMap是空的就进行初始化
        return setInitialValue();
    }

setInitialValue方法:

    private T setInitialValue() {
        //初始化value,实际是null
        T value = initialValue();
        //把刚初始化的null值set到ThreadLocalMap中
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        //返回value
        return value;
    }

initialValue方法:

    protected T initialValue() {
        return null;
    }

总结:ThreadLocal是多线程用来保存局部变量的一个类,而且保存的变量是具有隔离性的,线程独立的!

ThreadLocalMap是什么

通过ThreadLocal的set和get方法可以看到一个非常重要的类:ThreadLocalMap。实际上ThreadLocal的set和get方法都是通过这个map在操作数据,并且多线程Thread类的保存变量的属性就是ThreadLocalMap,所以它是连接ThreadLocal和Thread的桥梁。

 那么ThreadLocalMap到底是什么?

ThreadLocalMap是ThreadLocal的一个静态内部类,它内部还有一个Entry静态内部类,所以ThreadLocal对数据的操作实际上是ThreadLocalMap,而ThreadLocalMap的操作又是由Entry来完成的。

        /**
         * Entry继承了弱引用,通常把ThreadLocal对象作为key。
         * 注意:当entry.get()==null时说明这个key没有被引用了,是可以被回收的
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /**
             * 这个值是与ThreadLocal相关联的value
             */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap的关键方法:

set方法

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

        //table就是内部维护的Entry数组,用来存数据
        Entry[] tab = table;
        int len = tab.length;
        //根据ThreadLocal的哈希值和Entry数组的长度-1进行逻辑运算,算出数据下标
        int i = key.threadLocalHashCode & (len - 1);

        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            //如果key相同就直接替换value
            if (k == key) {
                e.value = value;
                return;
            }
            //如果e.get()为空则说明之前的Entry没有相关引用了(被称为StaleEntry),所以进行更新当前节点的Entry
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        //走到这说明在i的下标处没有数据,所以直接新增一个Entry
        tab[i] = new Entry(key, value);
        int sz = ++size;
        //cleanSomeSlots方法是清除被称为StaleEntry
        //如果cleanSomeSlots方法返回false即没有StaleEntry并且当前数据量大于阈值时进行rehash
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

get方法

    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
            //走到这有两种情况:1、Entry不同但是key相同,直接返回;2、key为null的stale节点,进行清除操作
            return getEntryAfterMiss(key, i, e);
    }

remove方法

    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)]) {
            //先清除节点然后再调用清除staleEntry的方法防止内存泄漏
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }

ThreadLocal的简单使用

多线程操作普通引用对象时会存在数据安全问题:

public class MyThread1 {
    static String str = "000";

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread1.str = String.valueOf(id);
                System.out.println("t1:" + MyThread1.str);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread1.str = String.valueOf(id);
                System.out.println("t2:" + MyThread1.str);
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread1.str = String.valueOf(id);
                System.out.println("t3:" + MyThread1.str);
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

}

结果:

t2:12
t3:13
t1:12

使用ThreadLocal来保证数据隔离:

public class MyThread2 {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread2.threadLocal.set(String.valueOf(id));
                System.out.println("t1:" + MyThread2.threadLocal.get());
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread2.threadLocal.set(String.valueOf(id));
                System.out.println("t2:" + MyThread2.threadLocal.get());
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                MyThread2.threadLocal.set(String.valueOf(id));
                System.out.println("t3:" + MyThread2.threadLocal.get());
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

}

结果:

t1:11
t2:12
t3:13

Thread和ThreadLocal、ThreadLocalMap的关系

Thread和ThreadLocal:ThreadLocal保证每个Thread的数据是隔离的。
Thread和ThreadLocalMap:Thread类有个成员变量是ThreadLocalMap,并以ThreadLocal为key来存储数据。
ThreadLocal和ThreadLocalMap:ThreadLocalMap是ThreadLocal的内部类,实际上ThreadLocal对数据的操作是通过ThreadLocalMap进行的。

补充:关于ThreadLocal如何解决内存泄漏的

什么是'内存泄漏'?

内存泄漏是指在编程过程中,程序动态分配的内存资源因为某些原因没有被正确地释放或回收,从而导致这些内存资源持续占据系统内存并逐渐积累,最终可能耗尽系统内存,影响程序的正常运行甚至是系统崩溃。内存泄漏不同于物理内存的消失,而是涉及到虚拟内存的使用情况。在Java开发中,通常所说的内存泄漏特指JVM(Java虚拟机)内存的管理问题。内存泄漏的原因可能是因为程序员在申请和分配内存后忘记释放,或者是因为内存管理机制的问题,如垃圾收集器无法有效地处理不再使用的内存。简而言之,内存泄漏是由于内存资源的未释放导致的系统内存浪费现象。

什么是‘弱引用’?

强引用(Strong Reference)和弱引用(Weak Reference)是两种不同的引用类型,它们的主要区别在于对象在被垃圾回收时的条件和时机。

  • 强引用 通常是通过 `new` 关键字创建的对象,它在对象被创建时就建立了引用关系。强引用使得垃圾回收器不会主动回收具有强引用的对象,即使在内存不足的情况下也不会触发垃圾回收。这是因为强引用认为对象是不可或缺的,因此垃圾回收器不会在没有强引用的情况下回收它。
  • 弱引用 与强引用相对,它是另一种较为宽松的引用方式。弱引用不会阻止垃圾回收器回收它所引用的对象,但是当垃圾回收器开始工作时,它会首先检查所有强引用为空的对象。如果发现只具有弱引用的对象,垃圾回收器会将其回收,无论当前内存是否充足。

总结来说,强引用是一种非常紧密的引用,几乎相当于对象不可缺少的一部分;而弱引用则是更加灵活,但在没有强引用保护的情况下,它的对象可能会被垃圾回收器回收。

 ThreadLocal是如何解决内存泄漏的?

1、采用弱引用。ThreadLocal对数据的操作实际上是ThreadLocalMap中Entry节点对数据的操作,而ThreadLocalMap中的Entry又继承了WeakReference(弱引用),所以即使存在内存泄漏问题也会被JVM垃圾回收掉;

2、清除方法。ThreadLocalMap的set、get、remove方法中都包含对key是否为空的判断,如果为空则为‘StaleEntry’然后被清除掉。

posted @ 2024-01-30 15:08  请别耽误我写BUG  阅读(18)  评论(0编辑  收藏  举报