首页 > 其他分享 >ThreadLocal阅读

ThreadLocal阅读

时间:2023-12-18 16:45:58浏览次数:35  
标签:阅读 Thread ThreadLocalMap ThreadLocal 线程 key Entry

ThreadLocal阅读

目录

首先基于ThreadLocal的简单使用场景,梳理一下下面三个的类之间的关系:

  1. Thread
  2. ThreadLocal
  3. ThreadLocalMap

Thread有一个ThreadLocalMap成员变量,来记录Thread在ThreadLocal上所拥有的资源:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

假设场景:有两个不同的数据库源,并且这俩数据库在业务代码中都得被多线程共享,那么自然会为这两个数据库分别设置一个ThreadLocal,来使得数据库连接在不同够的线程中隔离。

public class DB1 {
  public static final ThreadLocal<DB1Connection> tl;
}
public class DB1 {
  public static final ThreadLocal<DB2Connection> tl;
}

在线程中获取数据库连接的时候:

DB1Connection conn1 = db1.tl.get();
DB1Connection conn2 = db2.tl.get();

可以发现Thread与ThreadLocal形成一种多对多的关系,如何做到呢,就靠ThreadLocalMap(Thread的成员变量threadLocals)。这里先把它看成一个键为ThreadLocal值为Object的普通map。线程调用ThreadLocal.get的时候,get方法只需要:

Thread.currentThread().threadLocals.get(this);

就能获取到该Thread在这个ThreadLocal上对应的资源了(在这里是数据库连接)。

ThreadLocalMap

ThreadLocalMap是一个内部实现的一个map,ThreadLocal只用了ThreadLocalMap的如下三个方法:

  1. Entry getEntry(ThreadLocal<?> key)
  2. void set(ThreadLocal<?> key, Object value)
  3. void remove(ThreadLocal<?> key)

因此对于ThreadLocal来说这玩意可以看成一个键类型ThreadLocal值类型为Object的Map。

那么这个map如何被使用呢?由于一个Thread可以使用不同的ThreadLocalMap来获取资源,因此通过使用ThreadLocalMap作为Thread的成员变量,来记录该线程在各个ThreadLocal上所对应的资源。当线程使用ThreadLocal.get获取属于该线程的资源的时候,ThreadLocal就能通过该成员变量获取当前线程(Thread.currentThread())在该ThreadLocal(this)上对应的资源。

Thread.currentThread().threadLocals.get(this);

细究ThreadLocalMap

ThreadLocalMap用数组实现map,用线性探测法解决冲突,并且在复杂因子超过一定量的时候进行压缩甚至扩容。

最重要的成员变量table数组,数据元素是继承自WeakReference<ThreadLocal<?>>的Entry:

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

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

使用ThreadLocal弱引用而不是直接用ThreadLocal作为key的考量在于:ThreadLocal可能不会再被任何线程引用,此时就能检测到这个Entry变成了"stale"的,可以拿来复用了。

其他的成员变量size, threshold就没什么好介绍的了。

接下来看看上面说的最重要的三个方法。

getEntry

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.refersTo(key))
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

既然ThreadLocal作ThreadLocalMap的键,那第一步就是计算它的哈希值...哦原来不用计算,哈希值就是threadLocalHashCode成员变量,至于它是怎么初始化的文后再说,只需要知道它保存了这个ThreadLocal实例的哈希值就行。

然后看一下数组对应位置里面是否是这个key,如果不在的话有两种可能:

  1. 由于哈希冲突、压缩表、扩容表,导致该键被分配到了别的slot
  2. 根本不存在该键

因此下面的getEntryAfterMiss会遍历其他的位置找这个key,最终找不到就返回null。

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)]) {
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }

        if (e.refersTo(null)) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

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

看着比getEntry长一点,但其实也很简单:使用线性探测法,从哈希的位置开始找到可用的slot,如果是stale slot的话就直接替换。最后稍微压缩一下表(cleanSomeSlots),然后如果负载因子还是太大的话就继续压缩(rehash里调用expungeStaleEntries),负载因子还是还是太大的话,直接扩容成两倍大小(rehash里调用resize)!

remove

上面两个方法能看懂的话,remove更加不用说了,简单!

ThreadLocalMap其他的细节

  • Entry.refersTo(obj)方法:检测弱引用是否引用了对象obj,不用get() == obj的原因在于get返回对象的强引用,对GC不友好。如果你只是想检测一下,并不想要返回强引用的话,refersTo是最佳选择。
  • ThreadLocalMap其他方法比如rehash、expungeStaleEntry等,基本是用于压缩表啥的细枝末节,研究意义不大。
  • ThreadLocal的threadLocalHashCode怎么来的,以及ThreadLocal这个魔数怎么来的:What is the meaning of 0x61C88647 constant in ThreadLocal.java

ThreadLocal

ThreadLocal本身没什么可以探究的,稍微翻看一下源码就能理解,巧妙的点在于ThreadLocal为线程资源隔离提供了一个很好的思路。

下面说一下ThreadLocal的一些派生类:

SuppliedThreadLocal

配合ThreadLocal的静态方法withInitial使用,不想用匿名类并重载initialValue的方式创建ThreadLocal的话,可以使用withInitial

TerminatingThreadLocal

这个类作为ThreadLocal的一个扩展,除了基本的ThreadLocal资源隔离,还能让你在线程退出的时候做一些比如资源清理之类的事情。因此用户只需要重载这个方法:

protected void threadTerminated(T value) {}

当线程结束的时候,这个方法就会自动被调用。那么这是如何做到的呢?看看这个方法:

/**
 * Invokes the TerminatingThreadLocal's {@link #threadTerminated()} method
 * on all instances registered in current thread.
 */
public static void threadTerminated() {
    for (TerminatingThreadLocal<?> ttl : REGISTRY.get()) {
        ttl._threadTerminated();
    }
}

这个方法会在Thread.exit中被调用。其中REGISTRY.get()获取该线程所有注册的TerminatingThreadLocal,对每个TerminatingThreadLoca将资源取出来作为参数,回调用户注册好的方法:

private void _threadTerminated() { threadTerminated(get()); }

最后,注册会在创建资源的时候发生,remove的时候取消(资源已经被移除了,因此也没必要拿着null值去回调)

InheritableThreadLocal

ThreadLocal很明显只要是不同的线程get拿到的一定是不同的资源,因此InheritableThreadLocal提供了子线程想要拿到父线程的资源的方法。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    public InheritableThreadLocal() {}

    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

这个派生类很简单,只重载了这么几个方法,Thread.inheritableThreadLocals成员保存了可用于继承资源的ThreadLocal对应的ThreadLocalMap。

相比ThreadLocal.ThreadLocalMap的懒加载(在get、set时才会为线程创建ThreadLocalMap),InheritableThreadLocal.ThreadLocalMap会在线程构造函数中就被创建,并且当且仅当在线程的构造函数中继承父线程的资源:

private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
	// ...
  
  Thread parent = currentThread();
  
  // 在这里继承父线程在ThreadLocal中的资源
  if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals =
          ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  
  // ...
}


static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
  	// 注意这里,子线程的ThreadLocalMap使用的数组是新的实例,而不是指向parentMap的数组
  	// 因此继承只在线程构造的时刻完成,在这之后父子线程对ThreadLocal的操作毫无关系
    table = new Entry[len];

    for (Entry e : parentTable) {
        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++;
            }
        }
    }
}

如果用了InheritableThreadLocal,但是某个子线程并不想继承父线程的资源怎么办?Thread的构造函数不是有inheritThreadLocals可以控制这个行为吗hhhhhh...

标签:阅读,Thread,ThreadLocalMap,ThreadLocal,线程,key,Entry
From: https://www.cnblogs.com/nosae/p/17911593.html

相关文章

  • 阅读笔记:《代码大全》阅读笔记
    《代码大全》是我在软件开发领域的一本必读书籍。这本书几乎涵盖了软件开发的方方面面,从编码到设计、测试到调试等各个环节都有详细的讲解和指导。首先,我被作者对于代码的重视所深深吸引。他在书中强调,代码质量决定了软件的可靠性和可维护性。好的代码应该易读、易懂、易维护。通......
  • 《Effective Java》阅读笔记-第六章
    EffectiveJava阅读笔记第六章枚举和注解第34条用enum代替int常量int类型常量或者String类型常量作为参数的可读性和可维护性都比较差,甚至IDE都不好提示。Java中的枚举是完全单例,并且可以有字段、方法,以及实现接口(因为编译之后就是个类,并且自动继承了java.lang......
  • 阅读笔记(软件方法(上):业务建模和需求)
    在软件开发过程中,业务建模和需求分析是至关重要的阶段,它们为整个软件开发生命周期奠定了基础。本文将探讨业务建模和需求分析的关键概念,以及在软件方法论中常用的一些技术和工具。业务建模1.业务建模的定义业务建模是指对组织或企业的业务流程、结构和目标进行抽象和表达的过程......
  • 阅读笔记《掌握需求过程》
    《掌握需求过程》,又是一本新的书,这和我们现在上的课内容一致,都是讲解需求过程的。在试图构造产品之前,必须明确需求。如果没有正确的需求,就不能设计构造正确的产品,进而产品也就不能帮助用户完成他们的工作。然而已经有人证明,60%的错误在于需求和分析活动,大多数人选择,或更糟糕的是,他......
  • 阅读习惯
    本学期我的读书总时长增加了63小时,完整读完了4本书,做了40条笔记。通过阅读我对跟多方面的知识有了更一定的涉猎,这有助于拓宽视野,提升综合素养。每本书都是一个新的世界,通过阅读可以接触到不同的思想、文化、历史和各种主题。遇到陌生的词语我大都会标注出来,也会提出对于一些片段......
  • 软件工程读后感10-代码阅读方法与实践4
    最近,我阅读了代码阅读方法与实践的下一部分。意义重大的编码工作,或大型、有组织体制之下的项目,比如GNU和BSD,都会采纳一套编码规范、指导原则或约定。计算机语言和编程系统为程序员如何表达一个给定的算法提供了大量的余地。代码规范提供风格上的指导,目标是增强代码的可靠性、易读......
  • 对ThreadLocal的理解
    1.ThreadLocal概述ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal同时实现了线程内的资源共享案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每......
  • 阅读习惯2 20211314王艺达
    本学期读书总结本学期新增的读书时长主要在Apple自带的阅读app中,因为有一些想读的书,在微信读书中没有上架。微信读书平台:本学期微信读书主要阅读了两本书:笔记主要集中在《未竟的告白》这一书目中:以下是读完此书的小记:欲言又止,欲寄还休。以浮光掠影的方式扫完了整本......
  • SiReN Sign-Aware Recommendation Using Graph Neural Networks论文阅读笔记
    Abstract目前使用GNN的推荐系统主要利用高评分的正向用户-物品交互信息。但是如何利用低评分来表示用户的偏好是一个挑战,因为低评分仍然可以提供有用的信息。所以在本文中提出了基于GNN模型的有符号感知推荐系统SiReN,SiReN有三个关键组件构造一个符号二部图更精确的表示用户的......
  • 语文阅读理解总结
    需要注意的事项:1.答题思路要保持清晰,如果题目让分析词语的作用,首先应该写出这个词语的意思,再根据其释义分析作用例如:问:第六段加点词“越来越多”体现了说明文的准确性,请具体说明原始答案:越来越多说明人们更加地重视保护叫卖声,不希望叫卖声就此消失参考答案:越来越多表示数量逐......