当前位置: 代码网 > it编程>编程语言>Java > 并发编程模式之ThreadLocal源码和图文解读

并发编程模式之ThreadLocal源码和图文解读

2024年08月13日 Java 我要评论
从一道面试题开始吧,threadlocal使用需要注意什么,或者有什么问题?答:如果是在线程池中使用,会存在 1、内存泄漏 2、脏数据 的问题解决:try finally中调用 remove方法慢慢从

从一道面试题开始吧,threadlocal使用需要注意什么,或者有什么问题?

  • 答:如果是在线程池中使用,会存在 1、内存泄漏 2、脏数据 的问题
  • 解决:try finally中调用 remove方法

慢慢从threadlocal的设计和源码开始分析,

一、threadlocal结构和存取数据

当创建了一个threadlocal对象时,可以将存放的object对象、或者回调函数(延迟加载)放入setinitialvalue中;当当前线程去获取时,则会在当前线程thread的属性threadlocals中获取,而该属性的类型则为theadlocal的静态内部类threadlocalmap,但是引用还是指向了thread;而该threadlocals的threadlocalmap内部维护了一个其内部类threadlocal.threadlocalmap.entry数组,而entry由key和value组成,key即为当前的 new 的threadlocal对象本身,value为我们当前存储的object对象,并且key为weakreference(弱引用)类型。

所以,threadlocal本身只定义了一些内部类,而并不真实拥有任何数据,可以理解为全是空壳子;当获取数据时若当前线程的threadlocal对象(内存地址)不存在,则才会在该对象的setinitialvalue中copy一份值到thread的属性threadlocals中,key为当前对象引用,value为存储的object值;当创建了多个threadlocal对象时,当每当前线程都调用过threadlocal进行getset时,则会在当前线程的threadlocals中存储多个值,

如下图:

1、threadlocal#get

public t get() {
    thread var1 = thread.currentthread();
    threadlocal.threadlocalmap var2 = this.getmap(var1);
    if (var2 != null) {
        threadlocal.threadlocalmap.entry var3 = var2.getentry(this);
        if (var3 != null) {
            object var4 = var3.value;
            return var4;
        }
    }
 
    return this.setinitialvalue();
}

获取当前的线程,并且使用当前线程去获取threadlocal.threadlocalmap类型的对象,先看看getmap方法,

threadlocal.threadlocalmap getmap(thread var1) {
    return var1.threadlocals;
}

什么都没有做,只是将传入的当前对象的threadlocals属性返回,只是该引用还是指向了thread本身。

继续,

1、如果threadlocals对象本身不为null,则通过this,即当前threadlocal对象的引用为key,获取对应entry的值返回。

2、如果threadlocals对象本身为null,说明当前线程中没有存放过任何threadlocal对象的引入的值。

则需要调setinitialvalue方法,如下:

private t setinitialvalue() {
    object var1 = this.initialvalue();
    thread var2 = thread.currentthread();
    threadlocal.threadlocalmap var3 = this.getmap(var2);
    if (var3 != null) {
        var3.set(this, var1);
    } else {
        this.createmap(var2, var1);
    }
 
    return var1;
}

调用当前threadlocal对象的initialvalue方法,如果我们没有重写,则当前默认返回null,否则调用我们重写的方法,可以理解为懒加载。

每个线程第一次都会调用该方法获取的返回值,那么如果没次调用都是返回同一个对象则thread中存储的就是同一个对象,需要我们自己注意(如果同一对象线程a改变之后线程b也改变了),并且也存在线程安全的问题。

还是获取当前map是否为null,则调用createmap方法。

如下:

void createmap(thread var1, t var2) {
    var1.threadlocals = new threadlocal.threadlocalmap(this, var2);
}

2、threadlocal#set

理解完get的过程,set的就比较容易了,主要是引用本身比较绕。

public void set(t var1) {
    thread var2 = thread.currentthread();
    threadlocal.threadlocalmap var3 = this.getmap(var2);
    if (var3 != null) {
        var3.set(this, var1);
    } else {
        this.createmap(var2, var1);
    }
 
}

获取到thread的属性threadlocals,如果是null则new一个,否则就往map中放一个entry对象。

二、梳理存在的问题和解决

当在线程池中使用的时候,其实大部分情况下我们都会在线程池中使用,比如tomcat线程池。则key为弱引用,在root gc不可达的情况下,则会被jvm进行回收。但是正是因为如此value值将永远的游离,root gc永远指向不到该value值,则不能进行gc。当频繁的调用,则这样的对象越来越多,发生内存泄漏。如果该对象的内存还比较大,则会照成后续分配内存时新生代不足,则可能照成溢出的情况(溢出的个人理解)。

并且,当循环使用线程的话,比如存放的是用户信息,如果上一个用户请求离开时未清除数据,下一个用户进来直接获取的话会拿到上一个用户的数据,如果是存储的积分等,则完全就是脏数据

一并解决上面的两个问题方法比较简单,每次在调用代码时增加 try finally中调用 threadlocal对象的remove方法,如下:

threadlocal#remove

public void remove() {
    threadlocal.threadlocalmap var1 = this.getmap(thread.currentthread());
    if (var1 != null) {
        var1.remove(this);
    }
}

首先还是获取当前的线程去获取 thread的属性threadlocals调用其(threadlocalmap的)remove方法,如下:

private void remove(threadlocal<?> var1) {
    threadlocal.threadlocalmap.entry[] var2 = this.table;
    int var3 = var2.length;
    int var4 = var1.threadlocalhashcode & var3 - 1;
 
    for(threadlocal.threadlocalmap.entry var5 = var2[var4]; var5 != null; 
            var5 = var2[var4 = nextindex(var4, var3)]) {
        if (var5.get() == var1) {
            var5.clear();
            this.expungestaleentry(var4);
            return;
        }
    }
}

根据当前的threadlocal引用,去map中循环匹配,当匹配到之后,调用entry的remove方法,其实是调用refrenceclear方法。最后调用expungestaleentry方法将其对应的entry从数组中消除。

再看看referenceclear方法:

public void clear() {
    this.referent = null;
}

将该值直接置位null,则后续jvm会对该对象进行gc。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com