Java中的ThreadLocal源码及弱引用解析

 更新时间:2024年01月11日 10:35:45   作者:好奇的7号  
这篇文章主要介绍了Java中的ThreadLocal源码及弱引用解析,ThreadLocal类通过ThreadLocal可以实现全局变量在多线程环境下的线程隔离,每个线程都可以独立地访问和修改自己的全局变量副本,不会影响其他线程的副本,需要的朋友可以参考下

引言

我们知道在通常情况下,对于主存中的变量,每一个线程都能够访问并修改该变量(或对象)。

与之相对的,如果我们需要实现每个线程拥有自己专属的本地变量,该如何操作呢?

此时引出ThreadLocal类,通过ThreadLocal可以实现全局变量在多线程环境下的线程隔离。

每个线程都可以独立地访问和修改自己的全局变量副本,不会影响其他线程的副本。

这在某些场景下可以简化代码的编写和理解。

源码解析

示例代码

我们先从一段简单的代码示例入手:

package Thread_;
 
public class ThreadLocal {
    private static java.lang.ThreadLocal<Integer> counter = new java.lang.ThreadLocal<>();
 
    public static void main(String[] args) {
        Runnable runnable = () -> {
            // 获取当前线程的计数器值,初始值为0
            int count = counter.get() == null ? 0 : counter.get();
            System.out.println(Thread.currentThread().getName() + " 的计数器值为: " + count);
 
            // 对计数器进行累加操作
            counter.set(count + 1);
 
            // 模拟耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
 
            // 再次获取计数器值
            count = counter.get();
            System.out.println(Thread.currentThread().getName() + " 的累加后计数器值为: " + count);
        };
 
        // 创建三个线程并启动
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
 
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
 

//结果如下:
Thread-1 的计数器值为: 0
Thread-0 的计数器值为: 0
Thread-2 的计数器值为: 0
Thread-1 的累加后计数器值为: 1
Thread-2 的累加后计数器值为: 1
Thread-0 的累加后计数器值为: 1

在代码中,我们定义了一个ThreadLocal类的整形对象counter,用三个线程进行累加操作。

结果我们发现,counter的值并没有变为3,而是每个线程有一个自己的counter值,分别为1。

由此引出ThreadLoca的作用:

ThreadLocal对象在每个线程内是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己独有的ThreadLocal对象的值),即,实现了线程范围内的局部变量的作用。

线程独享原因

源码解析

首先我们可以推测,如果要保证每个线程独享一份数据,那这份数据应该要能够从线程内部进行引用。

实际上,线程的栈中存放了ThreadLocal.ThreadLocalMap这么一个属性(初始化为空),ThreadLocalMap是ThreadLocal的一个静态内部类,以后数据就要以ThreadLocalMap的形式在堆中实例化,并让threadLocals成为它的引用!这样就完成了线程的独享了。

public class Thread implements Runnable{
    ...
    ...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

我们回到示例中,注意:ThreadLocal<Integer> counter这个ThreadLocal对象的set方法,跟进源码,详细解释如下:

// 对计数器进行累加操作
counter.set(count + 1);
 
//源码1. set源码:
public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//让map成为该线程内的threadLocals所引用的堆中的对象!
//见源码2. 
//其实就是去尝试引用实例化的ThreadLocalMap,但此时初始化为null,所以我们看下面的判断:
 
        if (map != null) {
            map.set(this, value);//不为空,已经有map,就把堆中的值赋值为counter,count + 1
        } else {
            createMap(t, value);//为空,创建ThreadLocalMap的对象
            //但要注意,createMap并不是让map实例化,见下面源码3.
        }
    }
 
//源码2. getMap源码:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    //意思就是,返回这个t线程的ThreadLocal.ThreadLocalMap threadLocals = null里面这个threadLocals 对象
    }
 
//源码3. createMap源码:
//传入的this,其实就是counter。firstValue就是相应的值count + 1。
//注意,是将t.threadLocals线程内的这个ThreadLocalMap对象实例化,所以线程内部的这个对象指向了堆中内存的new...,实现了线程内部的数据独立。
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

概括:

看似是向ThreadLocal存入一个值,实际上是向当前线程对象中的ThreadLocalMap对象存入值(如果为空,则实例化当前线程对象中的ThreadLocalMap对象)ThreadLocalMap我们可以简单的理解成一个Map,Map存的key就是ThreadLocal实例本身(counter),value是具体的值。

get方法同理,也是先取出当前线程对象,再取出其指向的map里的值:

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

1.3 ThreadLocalMap的key的弱引用

源码如下:

static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

通常ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

ThreadLocal中一个设计亮点是ThreadLocalMap中的Entry结构的Key用到了弱引用。 试想如果使用强引用,如果ThreadLocalMap的Key使用强引用,那么Key对应的ThreadLocal对象在没有被外部引用时仍然无法被GC回收,因为Key存在于ThreadLocalMap中,而且线程是长时间存活的。这就可能导致ThreadLocal对象无法被回收,从而造成内存泄漏。

使用了弱引用的话,JVM触发GC回收弱引用后,ThreadLocalMap中会出现一些Key为null,但是Value不为null的Entry项,这些Entry项如果不主动清理,就会一直驻留在ThreadLocalMap中。此时,ThreadLocal在下一次调用get()、set()、remove()方法就可以删除那些ThreadLocalMap中Key为null的值,起到了惰性删除释放内存的作用。

到此这篇关于Java中的ThreadLocal源码及弱引用解析的文章就介绍到这了,更多相关ThreadLocal源码及弱引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Netty分布式行解码器逻辑源码解析

    Netty分布式行解码器逻辑源码解析

    这篇文章主要为大家介绍了Netty分布式行解码器逻辑源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • 在idea中创建SpringBoot模块的两种方式

    在idea中创建SpringBoot模块的两种方式

    这篇文章主要介绍了在idea中创建一个SpringBoot模块,本文给大家分享两种方式,每种方式分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • java中Calendar.add()方法的使用

    java中Calendar.add()方法的使用

    本文主要介绍了java中Calendar.add()方法的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Java编写多功能万年历程序的实例分享

    Java编写多功能万年历程序的实例分享

    这里我们来作一个Java编写多功能万年历程序的实例分享,可以查询公元历、农历、节气与节日等,十分全面,下面就来具体看一下:
    2016-06-06
  • springboot整合sentinel的方法教程

    springboot整合sentinel的方法教程

    这篇文章主要介绍了springboot整合sentinel的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Java中字符串去重的特性介绍

    Java中字符串去重的特性介绍

    这篇文章主要介绍了Java中字符串去重的特性,是Java8中引入的一个新特性,至于是否真的用起来顺手就见仁见智了...需要的朋友可以参考下
    2015-07-07
  • JAVA加密算法数字签名实现原理详解

    JAVA加密算法数字签名实现原理详解

    这篇文章主要介绍了JAVA加密算法数字签名实现原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Spring高阶用法之自定义业务对象组件化

    Spring高阶用法之自定义业务对象组件化

    这篇文章主要介绍了Spring高阶用法之自定义业务对象组件化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 详解Java并发之Condition

    详解Java并发之Condition

    这篇文章主要介绍了Java并发编程之Condition,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • JAVA 内部类详解及实例

    JAVA 内部类详解及实例

    这篇文章主要介绍了JAVA 内部类详解及实例的相关资料,需要的朋友可以参考下
    2016-11-11

最新评论