threadlocal with key of type_美团面试问ThreadLocal原理,这个回答我通过了面试!

前言

上周我侥幸通过美团一面,岗位是java后端开发工程师。美团面试官给我进行了二面。面试过程中他问了ThreadLocal原理(上次问线程池,这次问ThreadLocal,美团爸爸这么喜欢线程安全机制么),今天详细讲一讲ThreadLocal原理。

6f38da7e-acb8-49d3-93d6-77b058b192ff

ThreadLocal

ThreadLocal是线程的内部存储类,可以在指定线程内存储数据。只有指定线程可以得到存储数据。

/** * This class provides thread-local variables.  These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable.  {@code 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). */

每个线程都有一个ThreadLocalMap的实例对象,并且通过ThreadLocal管理ThreadLocalMap。

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

每个新线程都会实例化为一个ThreadLocalMap并且赋值给成员变量ThreadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。

应用场景

当某些数据是以线程为作用域并且不同线程有不同数据副本时,考虑ThreadLocal。

无状态,副本变量独立后不影响业务逻辑的高并发场景。

如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决。

get()与set()

set()是调用ThreadLocalMap的set()实现的

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }//getMap方法ThreadLocalMap getMap(Thread t) {      //thred中维护了一个ThreadLocalMap      return t.threadLocals; } //createMapvoid createMap(Thread t, T firstValue) {      //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals      t.threadLocals = new ThreadLocalMap(this, firstValue);}

ThreadLocalMap

ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标是value存储的对应位置。

1d9eaf5d966b4534a0054cafa505de6b

ThreadLocalMaps是延迟构造的,因此只有在至少要放置一个条目时才创建。

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;            setThreshold(INITIAL_CAPACITY);        }

ThreadLocalMap初始化时创建了默认长度是16的Entry数组。通过hashCode与length位运算确定索引值i。

每个Thread都有一个ThreadLocalMap类型。相当于每个线程Thread都有一个Entry型的数组table。而一切读取过程都是通过操作这个数组table进行的。

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);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                ThreadLocal> k = e.get();                //如果存在key则覆盖                if (k == key) {                    e.value = value;                    return;                }                if (k == null) {                    replaceStaleEntry(key, value, i);                    return;                }            }            //新建结点插入            tab[i] = new Entry(key, value);            int sz = ++size;            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

将threadLocalHashCode与长度进行位运算得到索引。

threadLocalHashCode的代码如下:

    private final int threadLocalHashCode = nextHashCode();    /**     * The next hash code to be given out. Updated atomically. Starts at     * zero.     */    private static AtomicInteger nextHashCode =        new AtomicInteger();    /**     * The difference between successively generated hash codes - turns     * implicit sequential thread-local IDs into near-optimally spread     * multiplicative hash values for power-of-two-sized tables.     */    private static final int HASH_INCREMENT = 0x61c88647;    /**     * Returns the next hash code.     */    private static int nextHashCode() {        return nextHashCode.getAndAdd(HASH_INCREMENT);    }

由于是static变量,threadLocalHashCode在每次加载threadLocal类时会重新初始化,同时会自增一次,增加HASH_INCREMENT(斐波那契散列乘数,通过该数散列出来的结果会比较均匀)。

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。

而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。

对于一个ThreadLocal来讲,他的索引值i是确定的。对于不同线程,同一个threadlocal对应的是不同table的同一下标,即是table[i],不同线程之间的table是相互独立的。

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

remove() 方法

/**  * Remove the entry for key.  */   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;           }       }   }

线程隔离特性

线程隔离特性,只有在线程内才能获取到对应的值,线程外不能访问。

(1)Synchronized是通过线程等待,牺牲时间来解决访问冲突

(1)ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突

内存泄露问题

存在内存泄露问题,每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

Demo程序

import java.util.concurrent.atomic.AtomicInteger;/** * 

Exper1

*

ThreadLocalId

* * @author : cxc * @date : 2020-04-01 23:48 **/ public class ThreadLocalId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal threadId = new ThreadLocal() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } public static void remove() { threadId.remove(); } }/** *

Exper1

*

总结

咱们玩归玩,闹归闹,别拿面试开玩笑。

ThreadLocal的原理在面试中几乎被问烂了。Thread的私有数据是存储在ThreadLocalMap,通过ThreadLoacl进行管理。要了解ThreadLocal的原理,最好多阅读几遍源码,尤其是ThreadLocalMap的源码部分。大家面试前要把知识点记牢。

b75e7f37-fe2d-4c97-81d7-823dabeb02b4

私信回复 资料 领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!

这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。

最后

欢迎大家一起交流,喜欢文章记得关注我点赞转发哟,感谢支持!

作者:天才程序YUAN」

原文:https://blog.csdn.net/JAck_chen0309/article/details/105257331

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值