ThreadLocal源码分析

本文深入剖析了ThreadLocal的工作原理,包括其初始化过程、get与set方法的具体实现,并通过一个实际示例展示了如何利用ThreadLocal为每个线程分配独立且唯一的ID。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天分析ThreadLocal

先看源码注释

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

大致意思是:这个类提供thread-local变量。这些变量不同于普通副本,在每个线程中,它是一个独立的初始化拷贝。

<p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the <tt>ThreadLocal</tt>
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).

只要线程是存活的并且ThreadLocal对象可以访问,那么每个线程拥有一个隐式的引用,指向thread-local变量。当线程结束后,所有thread-local对象的拷贝都会受制于gc。

写一个使用ThreadLocal的范例


public class UniqueThreadIdGenerator {
private static AtomicInteger uniqueId = new AtomicInteger(0);

private static ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
@Override
// 如果当前线程是第一次请求id的分配则给它赋一个初始值
protected Integer initialValue() {
System.out.println("initalValue");
return uniqueId.getAndIncrement();
}
};

// 给当前线程返回它的id
public static int getCurrentThreadId() {
return uniqueNum.get();
}

// 设置当前线程的id
public static void setCurrentThreadId(int id) {
uniqueNum.set(id);
}

}

public class Thread1 implements Runnable{

@Override
public void run() {
// 线程的id是在它第一次run的时候才分配的,它run,它请求分配id,系统给它一个id
int id = UniqueThreadIdGenerator.getCurrentThreadId();
System.out.println(Thread.currentThread().getName() + " is running, its ID is: " + id);

// 三次向系统请求数据
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is asking for data, my ID is:" + id);
}
System.out.println(Thread.currentThread().getName() + " is over!----------");
}


}


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


Thread ta = new Thread(new Thread1(),"A");
Thread tb = new Thread(new Thread1(),"B");
Thread tc = new Thread(new Thread1(),"C");

ta.start();
tb.start();
tc.start();

}



运行结果:
07-04 05:43:44.873 12683-12701/com.xlz.dagger2_test I/System.out: initalValue
    C is running, its ID is: 0
07-04 05:43:44.874 12683-12699/com.xlz.dagger2_test I/System.out: initalValue
07-04 05:43:44.875 12683-12699/com.xlz.dagger2_test I/System.out: A is running, its ID is: 1
07-04 05:43:44.883 12683-12700/com.xlz.dagger2_test I/System.out: initalValue
    B is running, its ID is: 2
07-04 05:43:45.874 12683-12701/com.xlz.dagger2_test I/System.out: C is asking for data, my ID is:0
07-04 05:43:45.876 12683-12699/com.xlz.dagger2_test I/System.out: A is asking for data, my ID is:1
07-04 05:43:45.884 12683-12700/com.xlz.dagger2_test I/System.out: B is asking for data, my ID is:2
07-04 05:43:46.877 12683-12701/com.xlz.dagger2_test I/System.out: C is asking for data, my ID is:0
07-04 05:43:46.878 12683-12699/com.xlz.dagger2_test I/System.out: A is asking for data, my ID is:1
07-04 05:43:46.887 12683-12700/com.xlz.dagger2_test I/System.out: B is asking for data, my ID is:2
07-04 05:43:47.879 12683-12701/com.xlz.dagger2_test I/System.out: C is asking for data, my ID is:0
07-04 05:43:47.880 12683-12701/com.xlz.dagger2_test I/System.out: C is over!----------
07-04 05:43:47.882 12683-12699/com.xlz.dagger2_test I/System.out: A is asking for data, my ID is:1
07-04 05:43:47.883 12683-12699/com.xlz.dagger2_test I/System.out: A is over!----------
07-04 05:43:47.889 12683-12700/com.xlz.dagger2_test I/System.out: B is asking for data, my ID is:2
    B is over!----------



可以看到,线程第一次使用ThreadLocal时会调用InitialValue方法

在获取线程相关的id时,是调用的ThreadLocal的get方法,我们以这个方法为入口,分析源码

/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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;
}
return setInitialValue();
}


get是返回当前线程的ThreadLocal变量的一个拷贝,如果这个变量没有值,则调用initalValue。

这里有个ThreadLocalMap,看下ThreadLocalMap是个啥

/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {


ThreadLocalMap是TheadLocal中的一个静态内部类,只适用于保存thread-local变量
每个Thead中都有一个ThreadLocalMap,看getMap(t)方法

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

就是获取Thread对象中的ThreadLocalMap成员

继续看get方法

if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);

如果map不为空,则从map中getEntry,看这个getEntry

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

就是从这个table中获取value,这个table是一个数组,用来存放value的,这个table是ThreadLocalMap中的成员

key是ThreadLocal对象

那么这样一来,由于ThreadLocalMap是每个线程独有的,即便ThreadLocal的相同,即key相同,但是table不同,取到的value也是线程独有的


上面分析的是map不为空的情况,如果map为空的话,会调用

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

这里先调用initValue()方法

protected T initialValue() {
return null;
}

initialValue方法是protected方法,这里默认返回null
可以在子类中重写这个方法,给ThreadLocal变量给一个初值

如果map为null,则调用createMap()
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看到,就是给Thread里面new了一个ThreadLocalMap

get方法的分析就到这里,可以知道ThreadLocal的get方法其实就是从Thead中ThreadLocalMap中获取value,每个Thread的ThreadLocalMap是不同,所以ThreadLocal获取的value也是每个线程独立的值


下面分析ThreadLocal的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);
}



这里可以看到,set方法是设置当前线程的TheradLocalMap里面的value,那么这个set方法就只与当前线程有关,与其它线程的值无关

访问真八字排盘系统介绍: 1、八字排盘 八字排盘是将年月日时按照天干地支的形式进行排列,一个时间单位代表了一个柱。八字由年柱、月柱、日柱和时柱共四个柱组成,也被称为四柱八字。八字学中基于中国阴阳五行、天干地支与刑冲克害、以及民间盲派的神煞论等方式,进一步预测爱情顺遂、工作高低、姻缘好坏、财富高低、学业成就、身体健康等事的学问。 八字排盘由以下元素组合:年月日时四柱、大运干支、胎元 、流年干支、十神、地势、神煞等。生辰八字不只是把干支历计算出来,而还要遵守月令、节令的强弱,时辰的阴阳变化进行校正。排盘分析,就是根据出生者的性别、天干地支的阴阳五行关系、进一步推算出来的一套方法论,给预测者做人生吉凶的参考数据,在未来事业、财运、婚姻、家庭等问题时,能做出风险较低的决策。 八字排盘怎么看 八字排盘由年、月、日、时四柱组成,每柱包含一个天干和一个地支,共八个字。年柱代表出生的年份,月柱代表出生的月份,日柱代表出生的日期,时柱则代表出生的时辰。每个柱的干支组合都会对个人的命运产生影响。天干地支旁边标注的正财、偏财、偏印、正印、比肩、劫财、食神、伤官、正官、七杀等,称为十神。 2、八字排盘软件介绍 我们是腾讯云市场金牌合作伙伴,广州正规软件开发公司,开发的八字排盘系统数据最全面精准,我们八字排盘采用最精确的排盘程序,而且运用“真太阳时”,进行更精确的时间划分。大家都知道我们使用的北京时间,是统一规定的标准时间。而八字排盘需要相对于太阳方位的天文时间,即平太阳时。我们国家地大物博,北京时间19时,哈尔滨已经夜幕降临,而新疆却还是太阳高挂,这时哈尔滨的天文时间可能在20:00以后,而新疆的天文时间可能在16时以前。北京时间是东经120度经线的平太阳时,如果您出生地的经度与北京时间所处的经度差异较大,或者处于单数时间点的前后,比如6点差一刻,8点,10点15分等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值