首页 > 其他分享 >Thread和ThreadLocal、ThreadLocalMap的关系

Thread和ThreadLocal、ThreadLocalMap的关系

时间:2024-01-30 15:13:20浏览次数:23  
标签:Thread ThreadLocalMap value ThreadLocal key Entry

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进行的。

 

标签:Thread,ThreadLocalMap,value,ThreadLocal,key,Entry
From: https://www.cnblogs.com/Bernard94/p/17994977

相关文章

  • ThreadPoolExecutor源码阅读
    目录简介继承结构ExecutorExecutorServiceAbstractExecutorServiceExecutorCompletionService线程池配置代码分析成员变量方法总结参考链接本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏......
  • C# AsyncLocal 是如何实现 Thread 间传值
    一:背景1.讲故事这个问题的由来是在.NET高级调试训练营第十期分享ThreadStatic底层玩法的时候,有朋友提出了AsyncLocal是如何实现的,虽然做了口头上的表述,但总还是会不具体,所以觉得有必要用文字+图表的方式来系统的说一下这个问题。二:AsyncLocal线程间传值1.线程间传值途径在C#编......
  • 标准库版thread group
    使用标准库替代boost中的threadgroup转载修改自秋天的栗树:用std::thread替换实现boost::thread_group使用unique_ptr替换了其中的auto_ptr(已不受支持);添加了<functional>和<algorithm>两个头文件;#include<list>#include<mutex>#include<thread>#include<memory>......
  • pthread_detach函数
     线程分离状态:指定该状态,线程主动与主控线程断开关系。使用pthread_exit或者线程自动结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。    进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资......
  • java中的ThreadLocal
    1.ThreadLocal的基本使用在Java的多线程并发执行过程中,为了保证多个线程对变量的安全访问,可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时而被另一个线程修改的现象。ThreadLocal类通常被翻译为线程本地变量类或者线程局部变......
  • C# AsyncLocal 是如何实现 Thread 间传值
    一:背景1.讲故事这个问题的由来是在.NET高级调试训练营第十期分享ThreadStatic底层玩法的时候,有朋友提出了AsyncLocal是如何实现的,虽然做了口头上的表述,但总还是会不具体,所以觉得有必要用文字+图表的方式来系统的说一下这个问题。二:AsyncLocal线程间传值1.线程间传值途径在......
  • ThreadLocal 在实战场景下的内存泄漏、逻辑混乱问题总结
    前言很早之前虽然看过ThreadLocal的源码,但是对于真实业务场景下可能存在的问题没有做过总结,刚好前几天在分析Mybatis内存泄漏的问题,想着ThreadLocal不是也可能会发生内存泄漏吗?于是乎本文出现了。本文相关博客1:ThreadLocal还存在内存泄漏?源码级别解读2:高质量实现单文件......
  • 深入理解Java中的ThreadLocal
    第1章:引言大家好,我是小黑。今天咱们来聊聊ThreadLocal。首先,让咱们先搞清楚,ThreadLocal是个什么玩意儿。简单说,ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着,每个线程都可以独立地改变自己的副本,而不会影响其他线程。这就像是每个人都有自己的笔记......
  • 运行新建Flutter项目, 报错Exception in thread “main“ java.net.ConnectException:
    新建项目后,直接使用demo进行安卓真机运行时报错Exceptioninthread"main"java.net.ConnectException:Connectiontimedout:connect atjava.base/sun.nio.ch.Net.connect0(NativeMethod) atjava.base/sun.nio.ch.Net.connect(Net.java:579) atjava.base/sun.nio.ch.N......
  • 条件变量pthread_cond_wait()和pthread_cond_signal()——需要和互斥锁一起使用——转
    条件变量pthread_cond_wait()和pthread_cond_signal()详解转载——原博客地址:https://www.cnblogs.com/cthon/p/9084735.html 条件变量     条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线......