当前位置: 首页 > news >正文

Java 线程 — ThreadLocal

ThreadLocal

先来看看ThreadLocal的注释:
This class provides** thread-local variables**. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻译过来就是:ThreadLocal提供了线程级的变量,这个变量和其他的使用set、get访问的变量不一样,ThreadLocal针对每个线程会为该变量维护经过单独初始化的副本。ThreadLocal实例希望定义为private static。

从注释可以看出ThreadLocal解决的问题是:

  • 单线程的变量共享

单线程变量共享

如果是变量共享为什么不用一个全局变量就好呢?主要是因为ThreadLocal为每个线程维护经过单独初始化的变量副本,但是普通的变量访问到的就是同一个。

上面的注释也说了,ThreadLocal会为每个线程维护经过单独初始化的变量副本,每个线程访问的都是自己的副本,所以各个线程之间不会相互影响,达到隔离的作用

线程同步解决的是多线程访问共享资源的问题,但是ThreadLocal本身并不是用来多线程之间共享的,只是用来单线程共享的,所以ThreadLocal和线程同步根本不是一回事儿

实现原理

  • 每个Thread都有一个ThreadLocal.ThreadLocalMap,因为Thread类里面有一个该类的对象,用来存放该线程中所有的ThreadLocal类型的变量

  • ThreadLocalMap里面有一个Entry(继承自WeakReference,一个对象保存一个键值对)数组,根据key(这里就是ThreadLocal变量本身)的哈希值将value(这里就是需要保存的数据)散列到数组中

  • 初次调用threadLocal.get的时候如果ThreadLocalMap尚未初始化,会调用createMap初始化

ThreadLocalMap初始化

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    // 如果map未初始化
    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;
}

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

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    // 新建初始大小为16的数组
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    // 设置扩容的阈值
    setThreshold(INITIAL_CAPACITY);
}

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
        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)
            // 如果key为null则进行一次清除
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

set

/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
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);

    // 使用的是开放地址法解决冲突,如果发生碰撞则向后查找
    // 如果得到的i位置已经有值,那么就向后一个单位尝试填充
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal k = e.get();
        // 如果是相同的key就替换,说明同一个对象中的
        // 同一个threadLocal变量
        if (k == key) {
            e.value = value;
            return;
        }
        // 因为是弱引用,ThreadLocal已经被回收,所以key就是null,将value放在这个位置
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 直到找到一个元素为空的位置(e == null),
    // 每新占用一个数组位置(上面都是在替换原来元素或者替换
    // 已经被移除的元素,size已经加过的)就要判断是否需要进行扩容
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 如果没有清除数组中的元素并且元素个数已经大于等于阈值threshold则进行扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

// 扫描数组清除陈旧的数据,但并不是全部扫描,而是log2(n)对数扫描
// 在全部扫描和不扫描之间取一个折中
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);
        }
    // 无符号右移一位,相当于每次除2取整
    } while ( (n >>>= 1) != 0);
    return removed;
}

// 在staleSlot到下一个数组元素为null之间,清空陈旧(key为null)的元素,
// 重新散列非陈旧的元素
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 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();
            // 将若引用置为null之后,进行一次清除
            expungeStaleEntry(i);
            return;
        }
    }
}

Java 中的引用

各种不同的引用的区别就是引用到的对象被垃圾回收的时机不同

  • 强引用:如果有强引用到一个对象,那么该对象不会被回收
  • 弱引用:只有弱引用链接的对象,在系统进行GC的时候就会被垃圾回收
  • 软引用:只有内存不够的时候,在会被GC回收
  • 虚引用:任何时候都可以被回收

神奇的0x61c88647

在ThreadLocalMap的hash算法中,很少发生碰撞,原因在于精巧的hash算法

private final int threadLocalHashCode = nextHashCode();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

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

这里最不解的是为什么要用0x61c88647这个数?怎么来的?
0x61c88647换算成十进制是1640531527,计算方法如下
1640531527 = (long) ((1L << 31) * (Math.sqrt(5) - 1))
(Math.sqrt(5) - 1) / 2 是黄金分割数
这种hash方法是Donald Knuth在 The Art of Computer Programming 中提出,不明觉厉

问题

清除stale数组元素的标准就是 key == null,什么时候ThreadLocal.key为null?
key为null:因为key是ThreadLocal的弱引用,当ThreadLocal没有强引用的时候,ThreadLocal变量可能会被回收,这个时候出现了key为null的情况

转载于:https://www.cnblogs.com/sunshine-2015/p/6072184.html

相关文章:

  • 前端学数据库之数据表操作
  • [转]C#中捕捉对话框的文本内容 EnumChildWindows
  • (转)LINQ之路
  • 创建dialog
  • SQL Scripts Template Files Path
  • OS命令注入中的空格
  • //解决validator验证插件多个name相同只验证第一的问题
  • IE6 jQuery append()函数 与 JS appendChild(elem) 函数 报错原因
  • 管理者的最基本职责是什么?
  • spark
  • redis查看数据
  • C# 获取listview中选中一行的值
  • 火锅惹的祸
  • 使用sqlserver的游标功能来导数据的常见写法
  • 深入理解mybatis参数
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 〔开发系列〕一次关于小程序开发的深度总结
  • 345-反转字符串中的元音字母
  • Fabric架构演变之路
  • java取消线程实例
  • java中具有继承关系的类及其对象初始化顺序
  • js如何打印object对象
  • Lucene解析 - 基本概念
  • Redis学习笔记 - pipline(流水线、管道)
  • vue 配置sass、scss全局变量
  • Vue.js源码(2):初探List Rendering
  • Vue小说阅读器(仿追书神器)
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 爬虫模拟登陆 SegmentFault
  • 设计模式(12)迭代器模式(讲解+应用)
  • 手机端车牌号码键盘的vue组件
  • Java性能优化之JVM GC(垃圾回收机制)
  • MyCAT水平分库
  • scrapy中间件源码分析及常用中间件大全
  • 大数据全解:定义、价值及挑战
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (11)MATLAB PCA+SVM 人脸识别
  • (day 12)JavaScript学习笔记(数组3)
  • (笔试题)合法字符串
  • (层次遍历)104. 二叉树的最大深度
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (三)终结任务
  • (一)WLAN定义和基本架构转
  • (转载)Google Chrome调试JS
  • **python多态
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net core 6.0 升8.0
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题