Thread和ThreadLocal、ThreadLocalMap的关系

Thread和ThreadLocal、ThreadLocalMap的关系

ThreadLocal是什么

ThreadLocal官方注释:

翻译过来大致意思是:ThreadLocal可以提供局部变量,通过set和get方法对局部变量进行操作,并且局部变量是每个线程独立的、数据隔离的。ThreadLocal通常作为线程的私有的静态变量,用于和UserId、事务Id相关联。

set方法:

public void set(T value) {

//获取当前线程

Thread t = Thread.currentThread();

//获取ThreadLocalMap

ThreadLocal.ThreadLocalMap map = getMap(t);

//如果ThreadLocalMap不是空则直接把当前ThreadLocal作为key存到map中

if (map != null)

map.set(this, value);

else

//如果ThreadLocalMap是空,就初始化map

createMap(t, value);

}

getMap方法:

//获取ThreadLocalMap就是获取当前线程的threadLocals属性

ThreadLocal.ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

createMap方法:

void createMap(Thread t, T firstValue) {

//新增一个ThreadLocalMap,把当前ThreadLocal作为key存到map中

//并且把新增的ThreadLocalMap作为当前线程的threadLocals属性

t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);

}

get方法:

public T get() {

//获取当前线程

Thread t = Thread.currentThread();

//获取ThreadLocalMap

ThreadLocal.ThreadLocalMap map = getMap(t);

//如果ThreadLocalMap不为空就通过当前ThreadLocal对象获取Entry,返回Entry的值

if (map != null) {

ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T) e.value;

return result;

}

}

//如果ThreadLocalMap是空的就进行初始化

return setInitialValue();

}

setInitialValue方法:

private T setInitialValue() {

//初始化value,实际是null

T value = initialValue();

//把刚初始化的null值set到ThreadLocalMap中

Thread t = Thread.currentThread();

ThreadLocal.ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

//返回value

return value;

}

initialValue方法:

protected T initialValue() {

return null;

}

总结:ThreadLocal是多线程用来保存局部变量的一个类,而且保存的变量是具有隔离性的,线程独立的!

ThreadLocalMap是什么

通过ThreadLocal的set和get方法可以看到一个非常重要的类:ThreadLocalMap。实际上ThreadLocal的set和get方法都是通过这个map在操作数据,并且多线程Thread类的保存变量的属性就是ThreadLocalMap,所以它是连接ThreadLocal和Thread的桥梁。

那么ThreadLocalMap到底是什么?

ThreadLocalMap是ThreadLocal的一个静态内部类,它内部还有一个Entry静态内部类,所以ThreadLocal对数据的操作实际上是ThreadLocalMap,而ThreadLocalMap的操作又是由Entry来完成的。

/**

* Entry继承了弱引用,通常把ThreadLocal对象作为key。

* 注意:当entry.get()==null时说明这个key没有被引用了,是可以被回收的

*/

static class Entry extends WeakReference> {

/**

* 这个值是与ThreadLocal相关联的value

*/

Object value;

Entry(ThreadLocal k, Object v) {

super(k);

value = v;

}

}

ThreadLocalMap的关键方法:

set方法

private void set(ThreadLocal key, Object value) {

//table就是内部维护的Entry数组,用来存数据

Entry[] tab = table;

int len = tab.length;

//根据ThreadLocal的哈希值和Entry数组的长度-1进行逻辑运算,算出数据下标

int i = key.threadLocalHashCode & (len - 1);

for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {

ThreadLocal k = e.get();

//如果key相同就直接替换value

if (k == key) {

e.value = value;

return;

}

//如果e.get()为空则说明之前的Entry没有相关引用了(被称为StaleEntry),所以进行更新当前节点的Entry

if (k == null) {

replaceStaleEntry(key, value, i);

return;

}

}

//走到这说明在i的下标处没有数据,所以直接新增一个Entry

tab[i] = new Entry(key, value);

int sz = ++size;

//cleanSomeSlots方法是清除被称为StaleEntry

//如果cleanSomeSlots方法返回false即没有StaleEntry并且当前数据量大于阈值时进行rehash

if (!cleanSomeSlots(i, sz) && sz >= threshold)

rehash();

}

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

//走到这有两种情况:1、Entry不同但是key相同,直接返回;2、key为null的stale节点,进行清除操作

return getEntryAfterMiss(key, i, e);

}

remove方法

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)]) {

//先清除节点然后再调用清除staleEntry的方法防止内存泄漏

if (e.get() == key) {

e.clear();

expungeStaleEntry(i);

return;

}

}

}

ThreadLocal的简单使用

多线程操作普通引用对象时会存在数据安全问题:

public class MyThread1 {

static String str = "000";

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread1.str = String.valueOf(id);

System.out.println("t1:" + MyThread1.str);

}

});

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread1.str = String.valueOf(id);

System.out.println("t2:" + MyThread1.str);

}

});

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread1.str = String.valueOf(id);

System.out.println("t3:" + MyThread1.str);

}

});

t1.start();

t2.start();

t3.start();

}

}

结果:

t2:12

t3:13

t1:12

使用ThreadLocal来保证数据隔离:

public class MyThread2 {

static ThreadLocal threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread2.threadLocal.set(String.valueOf(id));

System.out.println("t1:" + MyThread2.threadLocal.get());

}

});

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread2.threadLocal.set(String.valueOf(id));

System.out.println("t2:" + MyThread2.threadLocal.get());

}

});

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

long id = Thread.currentThread().getId();

MyThread2.threadLocal.set(String.valueOf(id));

System.out.println("t3:" + MyThread2.threadLocal.get());

}

});

t1.start();

t2.start();

t3.start();

}

}

结果:

t1:11

t2:12

t3:13

Thread和ThreadLocal、ThreadLocalMap的关系

Thread和ThreadLocal:ThreadLocal保证每个Thread的数据是隔离的。Thread和ThreadLocalMap:Thread类有个成员变量是ThreadLocalMap,并以ThreadLocal为key来存储数据。ThreadLocal和ThreadLocalMap:ThreadLocalMap是ThreadLocal的内部类,实际上ThreadLocal对数据的操作是通过ThreadLocalMap进行的。

补充:关于ThreadLocal如何解决内存泄漏的

什么是'内存泄漏'?

内存泄漏是指在编程过程中,程序动态分配的内存资源因为某些原因没有被正确地释放或回收,从而导致这些内存资源持续占据系统内存并逐渐积累,最终可能耗尽系统内存,影响程序的正常运行甚至是系统崩溃。内存泄漏不同于物理内存的消失,而是涉及到虚拟内存的使用情况。在Java开发中,通常所说的内存泄漏特指JVM(Java虚拟机)内存的管理问题。内存泄漏的原因可能是因为程序员在申请和分配内存后忘记释放,或者是因为内存管理机制的问题,如垃圾收集器无法有效地处理不再使用的内存。简而言之,内存泄漏是由于内存资源的未释放导致的系统内存浪费现象。

什么是‘弱引用’?

强引用(Strong Reference)和弱引用(Weak Reference)是两种不同的引用类型,它们的主要区别在于对象在被垃圾回收时的条件和时机。

强引用 通常是通过 `new` 关键字创建的对象,它在对象被创建时就建立了引用关系。强引用使得垃圾回收器不会主动回收具有强引用的对象,即使在内存不足的情况下也不会触发垃圾回收。这是因为强引用认为对象是不可或缺的,因此垃圾回收器不会在没有强引用的情况下回收它。

弱引用 与强引用相对,它是另一种较为宽松的引用方式。弱引用不会阻止垃圾回收器回收它所引用的对象,但是当垃圾回收器开始工作时,它会首先检查所有强引用为空的对象。如果发现只具有弱引用的对象,垃圾回收器会将其回收,无论当前内存是否充足。

总结来说,强引用是一种非常紧密的引用,几乎相当于对象不可缺少的一部分;而弱引用则是更加灵活,但在没有强引用保护的情况下,它的对象可能会被垃圾回收器回收。

ThreadLocal是如何解决内存泄漏的?

1、采用弱引用。ThreadLocal对数据的操作实际上是ThreadLocalMap中Entry节点对数据的操作,而ThreadLocalMap中的Entry又继承了WeakReference(弱引用),所以即使存在内存泄漏问题也会被JVM垃圾回收掉;

2、清除方法。ThreadLocalMap的set、get、remove方法中都包含对key是否为空的判断,如果为空则为‘StaleEntry’然后被清除掉。

相关推荐

手机qq怎么查看成为好友多少天 手机QQ好友成为多少天
烟草码地区编码缩写
365bet信誉怎么样

烟草码地区编码缩写

08-01 👁️‍🗨️ 6722
新亚电缆在深交所上市
365bet体育滚球

新亚电缆在深交所上市

07-30 👁️‍🗨️ 2882
索尼EX251点评( SONYEX251 )
365bet体育滚球

索尼EX251点评( SONYEX251 )

06-30 👁️‍🗨️ 5218
自然资源部办公厅 国家林业和草原局办公室关于加强协调联动进一步做好建设项目用地审查和林地审核工作的通知
烟草码地区编码缩写
365bet信誉怎么样

烟草码地区编码缩写

08-01 👁️‍🗨️ 6722
平方米和平方厘米的换算
365bet体育滚球

平方米和平方厘米的换算

06-28 👁️‍🗨️ 3014
在 PowerPoint 的幻灯片放映中跨多个幻灯片播放音乐
mobile.365-588

在 PowerPoint 的幻灯片放映中跨多个幻灯片播放音乐

07-06 👁️‍🗨️ 9848
2025热门自驾导航地图软件推荐:好用的自驾导航地图软件有哪些