java.lang.ThreadLocal

news2024/11/15 13:44:42

ThreadLocal万字总结icon-default.png?t=N7T8https://blog.csdn.net/sinat_33921105/article/details/103295070

key的唯一性

一个线程中的多个ThreadLocal变量如何存储、如何保证唯一性?

每一个 ThreadLocal<T> tl = new ThreadLocal<>(); 创建出来都有一个不变且唯一的threadLocalHashCode,这个threadLocalHashCode在本线程局部变量的存储中作为唯一识别标志参与到key-value存储的key的计算,非常重要!

public class ThreadLocal<T> {
   
     private final int threadLocalHashCode = nextHashCode();
     private static AtomicInteger nextHashCode = new AtomicInteger();
     private static final int HASH_INCREMENT = 0x61c88647; 
     private static int nextHashCode() {
      return nextHashCode.getAndAdd(HASH_INCREMENT);
     }
     
     public ThreadLocal() {
     }
}

线程独有

从源码的角度看,为什么ThreadLocal变量是线程独有,不同线程之间不会互相干扰,降低编程复杂性和从根源上避免线程安全问题。

 ThreadLocal.set(value)取得是当前线程的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);
    }

 取当前线程的ThreadLocalMap:

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

当前线程中ThreadLocalMap的定义:

public class Thread implements Runnable {   
     ThreadLocal.ThreadLocalMap threadLocals = null;
}

 当前线程中ThreadLocalMap如果不存在创建:

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

set方法原理

以当前变量为key存储在当前Thread.ThreadLocalMap中的键值对。key的唯一性通过threadLocalHashCode与容量的计算来保证。

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

作为下标存储在数组中,这是threadLocalHashCode唯一性重要性的体现,与数组的容量取余操作保证下标不越界(如果容量不足会有其他方法进行扩容)

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
//阈值,容量超过threshold的3/4会扩容,threshold = INITIAL_CAPACITY * 2/3;
            setThreshold(INITIAL_CAPACITY);
        }

ThreadLocalMap.set:当ThreadLocal变量中存在Map时会直接调用Map的set方法,

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

            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;
                }
                //set方法会清除泄露的值
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            //容量大于阈值,扩容需要重新散列存储
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

get方法原理

知道了set的存储机制,get就容易理解了,直接通过核心代码表示:

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.getEntry方法:

 private Entry getEntry(ThreadLocal<?> key) {
            //依然通过threadLocalHashCode计算获取下标
            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);
        }

remove方法

建议大家使用最后都主动调用ThreadLocal.remove()方法,防止内存泄露,虽然ThreadLocal是弱引用,每次GC都会回收,set在ThreadLocal里的数据并没有被同时清除。详细了解可以看文章开头引用的文章。

Java的四种引用类型

强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知

以下是源码其他方法上文未作出解释的,如果需要自行查看。


public class ThreadLocal<T> {
   
    protected T initialValue() {
        return null;
    }

  
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

     
    public ThreadLocal() {
    }
 
    
 
    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;
    }
 
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
 
  
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

  
    static class ThreadLocalMap {

       
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

    
        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;

        private int size = 0;

        private int threshold; // Default to 0

        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
 
       
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

  
        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)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        
 
        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();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
 
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}

​​​​​​​​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1950697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Codeforces Round 949 (Div. 2) B. Turtle and an Infinite Sequence (找规律,位运算)

不知道为什么会有找规律这种nt题型。 首先每一秒都会发生 a i a i − 1 ∣ a i ∣ a i 1 a_i a_{i-1} | a_i | a_{i1} ai​ai−1​∣ai​∣ai1​&#xff0c;如果我们多写几步&#xff1a; a i 2 a i − 1 1 ∣ a i 1 ∣ a i 1 1 a i − 2 ∣ a i − 1 ∣ a i ∣ a i −…

力扣高频SQL 50 题(基础版)第三题

文章目录 力扣高频SQL 50 题&#xff08;基础版&#xff09;第三题1148.文章浏览题目说明思路分析实现过程准备数据实现方式结果截图 力扣高频SQL 50 题&#xff08;基础版&#xff09;第三题 1148.文章浏览 题目说明 Views 表&#xff1a; ---------------------- | Colu…

用 python 求拥塞控制模型欧拉数值解

昨天使用 scipy 的 odeint 模拟了 E_best 的微分方程组模型(参见 用 python scipy 库模拟拥塞控制模型)&#xff0c;但我觉得那个模型中处理 z 时不够优雅&#xff0c;只是一个负反馈&#xff0c;并未体现 “排队时延与 buffer 占用率成比例增长” 的事实&#xff0c;所以今天我…

基于NE555制作雾化加湿器

基于NE555制作雾化加湿器 &#x1f33c;实物制作图&#xff1a;&#xff08;只焊接了2路&#xff0c;进行功能验证&#xff09; &#x1f4fa;演示效果&#xff1a; &#x1f341;原理图&#xff1a; &#x1f389;其他方案&#xff1a;基于专用加湿器芯片&#xff1a;富…

noVNC使用与介绍

noVNC使用与介绍报告 1. 概述 VNC&#xff08;Virtual Network Console&#xff0c;虚拟网络控制台&#xff09;是一种流行的远程桌面访问协议&#xff0c;它允许用户通过网络连接到远程计算机的图形界面。VNC协议的实现通常包括两个主要组件&#xff1a;服务器端&#xff08…

充满惊喜与欢乐的老友

在这个充满惊喜与欢笑的娱乐圈里&#xff0c;每一个不经意的可能成为网友热议的焦点&#xff0c;而《快乐老友记》的花絮&#xff0c;无疑为这个多彩的世界又添上了一抹亮丽的色彩。当“王栎鑫被路人认成张艺兴”这一话题如春风般拂过网络&#xff0c;不仅让两位才华横溢的艺人…

硬核科普:什么是网络准入控制系统|网络准入控制系统四大品牌介绍

网络准入控制系统&#xff08;Network Access Control, NAC&#xff09;是一种用于确保只有授权设备和用户才能接入网络的安全技术。 本文将介绍几种常用的网络准入控制系统&#xff0c;帮助您更好地了解如何选择适合您企业的NAC系统。 网络准入控制的重要性和作用 网络准入控…

Jenkins集成JDK、git、gitee、maven逐步实现自动拉取,自动部署,自动启动

1. jenkins集成JDK 成功登录Jenkins后&#xff0c;选择Manage Jenkins&#xff0c;选择Tools 集成JDK 2. jenkins集成git 因为Jenkins自动从git远程仓库拉取代码 首先要在Jenkins所在的linux服务器上安装git yum install -y git然后&#xff0c;实行集成JDK的第一步 配置g…

ElMessage自动引入,样式缺失和ts esline 报错问题解决

一. 环境 "unplugin-auto-import": "^0.17.6", "vue": "^3.3.8", "vite": "^5.0.0", "typescript": "^5.2.2",二. ElMessage样式缺失问题. 以下有两种解决方法 方法一: 配置了自动引用后…

【Java算法专场】二分查找(上)

目录 前言 什么是二分查找&#xff1f; 二段性 ​​​​​​​​​​​​​​​​​​​​​二分查找 算法分析 算法步骤 算法代码 算法示例 模板 在排序数组中查找元素的第一个和最后一个位置 算法分析 算法步骤 算法代码 算法示例 搜索插入位置 算法分析 算法步…

HTML常见标签——超链接a标签

一、a标签简介 二、a标签属性 href属性 target属性 三、a标签的作用 利用a标签进行页面跳转 利用a标签返回页面顶部以及跳转页面指定区域 利用a标签实现文件下载 一、a标签简介 <a>标签用于做跳转、导航&#xff0c;是双标签&#xff0c;记作<a></a>&#…

MFC开发,自定义消息

在MFC开发中&#xff0c;主要核心机制就是消息机制。QT与之类似的机制就是信号与槽。QT中的信号与槽是非常容易自定义的&#xff0c;MFC也是如此&#xff0c;自定义也是比较方便&#xff0c;况且自定义消息或者控件在整个GUI图形化界面开发中也是非常重要的部分&#xff0c;上篇…

项目都做完了,领导要求国际化????--JAVA后端篇

springboot项目国际化相信各位小伙伴都会&#xff0c;很简单&#xff0c;但是怎么项目都做完了&#xff0c;领导却要求国际化文件就很头疼了 国际化的SpringBoot代码&#xff1a; 第一步&#xff1a;创建工具类 /*** 获取i18n资源文件** author bims*/ public class Message…

Java Collections类

Collections是一个与有关集合的工具类&#xff0c;提供了很多对集合进行操作的方法。 常见方法 addAll&#xff1a;往集合中添加多个元素。 public static <T> boolean addAll(Collection<? super T> c, T... elements) {boolean result false;for (T element…

MongoDB教程(二十):MongoDB正则表达式

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、正则表…

Python3网络爬虫开发实战(3)网页数据的解析提取

文章目录 一、XPath1. 选取节点2. 查找某个特定的节点或者包含某个指定的值的节点3. XPath 运算符4. 节点轴5. 利用 lxml 使用 XPath 二、CSS三、Beautiful Soup1. 信息提取2. 嵌套选择3. 关联选择4. 方法选择器5. css 选择器 四、PyQuery1. 初始化2. css 选择器3. 信息提取4. …

程序的机器级表示(一)汇编,汇编格式和数据传输指令

系列文章 : 深入理解计算机系统笔记 文章目录 系列文章3 程序的机器级表示3.1 历史观点3.2 程序编码3.2.1 机器级代码3.2.2 代码示例3.2.3 关于格式的注解 3.3 数据格式3.4 访问信息3.4.1 操作数指示符3.4.2 数据传送指令3.4.3 数据传送示例3.4.4 压入和弹出栈数据 3 程序的机…

如何做校园圈子小程序,需要哪些功能?可打包APP小程序H5,源码交付,支持二开!

独立学校首页 支持每个学校独立首页!每个学校都可以拥有专属首页&#xff0c;打造不同风格的学校首页展示效果 多业务覆盖 可实现校园内外卖、跑腿、超市、药店水果、快餐店等业务全覆盖!所有配送业务平台都可开展 多站点运营 支持多学校多站点运营&#xff0c;各分站管理员可独…

【日记】办个护照不至于有这种刑事罪犯一样的待遇吧……(737 字)

正文 暴晒&#xff0c;中午出去骑共享单车&#xff0c;座垫都不敢坐。 至于为什么&#xff0c;中午觉都不睡跑出去&#xff0c;是因为今天他们办承兑汇票的业务&#xff0c;搞了一天&#xff0c;中午不休息&#xff0c;说可能还会用到我的指纹&#xff0c;让我 on call。我心想…

基础IO(文件系统)

一、块组的宏观理解 1、分区和分组 首先一台电脑就一个磁盘&#xff0c;一般800GB到1TB&#xff0c;为了管理这么大的内存数据&#xff0c;我们就对磁盘进行分区&#xff0c;分区之后才是我们看到的C盘&#xff0c;D盘等。 但是其实分区之后空间还是太大不好管理&#xff0c;…