详解Java中ThreadLocal类型及简单用法

 更新时间:2021年10月14日 00:00  点击:1408

1 基本概念

ThreadLocal类提供了线程局部变量。这些变量与普通变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程关联起来的类中的私有静态字段(例如,用户ID或事务ID)。

例如,下面的类生成每个线程本地的唯一标识符。 线程的id在第一次调用ThreadId.get()时被赋值,并且在后续调用中保持不变。

public class ThreadId {
    // 包含要分配的下一个线程ID的原子整数
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // 包含每个线程ID的线程局部变量
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override 
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    // 返回当前线程的唯一ID,并在必要时赋值
    public static int get() {
        return threadId.get();
    }
}

只要线程是活的并且ThreadLocal实例是可访问的,每个线程都持有一个对线程局部变量副本的隐式引用; 当一个线程离开后,它的所有线程本地实例副本都将被垃圾收集(除非存在对这些副本的其他引用)

2 简单使用

private static ThreadLocal<String> threadLocal = new ThreadLocal();

private static void print(String thread) {
    //打印当前线程中本地内存中本地变量的值
    System.out.println(thread + " :" + threadLocal.get());
    //清除本地内存中的本地变量
    threadLocal.remove();
}

public static void main(String[] args) {
    new Thread(() -> {
        //设置线程1副本变量的值
        threadLocal.set("I am Thread1");
        //调用打印方法
        print("thread1");
        //打印本地变量
        System.out.println("after remove : " + threadLocal.get());
    }).start();

    new Thread(() -> {
        //设置线程2副本变量的值
        threadLocal.set("I am Thread2");
        //调用打印方法
        print("thread2");
        System.out.println("after remove : " + threadLocal.get());
    }).start();
}

运行结果:

thread1 :I am Thread1
thread2 :I am Thread2
after remove : null
after remove : null

由上边的程序可以看出,ThreadLocal就是将本地变量在多线程访问条件下给每个线程一个副本变量,图示:

在这里插入图片描述

3 应用场景

最典型应用场景就是Spring的声明式事务、 解决数据库连接、Session管理

那数据库链接为例:

将数据库的链接示例在每个线程中都有一份副本数据,在某一个线程关闭连接的时候就不会关闭其他链接,session同理。

private static final ThreadLocal<Connection> connection = new ThreadLocal() {
    @Override
    protected Object initialValue() {
        return Connection.getConnection();
    }
};

private static Connection getConnections() {
    return connection.get();
}

public static void main(String[] args) {
    new Thread(() -> {
        Connection connection = getConnections();
        //不会关闭其他链接
        connection.close();
    }).start();
    new Thread(() -> {
        Connection connection = getConnections();
        connection.close();
    }).start();
}

4 底层原理

ThreadLocal类型主要有3个方法和一个数据结构,分别是get()、set(Object)、remove()及ThreadLocalMap

4.1 set(Object)

将该线程局部变量的当前线程副本设置为指定的值。 大多数子类都不需要重写这个方法,只依赖于initialValue方法来设置线程局部变量的值。

参数: Value -要存储在当前线程本地线程的副本中的值。

public void set(T value) {
    //获取调用者线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}

4.2 get()

返回该线程局部变量的当前线程副本中的值。 如果变量在当前线程中没有值,则首先将其初始化为initialValue方法调用所返回的值。

返回: 这个线程本地的当前线程值

public T get() {
    Thread t = Thread.currentThread();
    //获取当前线程的threadLocals变量	
    ThreadLocalMap map = getMap(t);
    //如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

4.3 remove()

移除此线程局部变量的当前线程值。如果这个线程局部变量随后被当前线程读取,它的值将通过调用它的initialValue方法重新初始化,除非它的值是由当前线程在中间设置的。这可能导致在当前线程中多次调用initialValue方法。

public void remove() {
    //获取当前线程绑定的threadLocals
    ThreadLocalMap m = getMap(Thread.currentThread());
    //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
    if (m != null)
        m.remove(this);
}

4.4 ThreadLocalMap

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private static final int INITIAL_CAPACITY = 16;
    //有效Entry数组
    private Entry[] table;
    //大小
    private int size = 0;
    //负载因子
    private int threshold; // Default to 0
}

5 内存泄漏隐患和防止策略

5.1 为什么会发生内存泄漏?

在线程池中线程的存活时间太长,往往都是和程序同生共死的,这样 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。
Entry 中的 Value 是被 Entry 强引用的,即便 value 的生命周期结束了,value 也是无法被回收的,导致内存泄露。

5.2 怎样防止内存泄漏?

  • ThreadLocal申明为private static final xxx
  • ThreadLocal使用后务必调用remove方法。

参考文章:

https://www.cnblogs.com/fsmly/p/11020641.html

https://blog.csdn.net/meism5/article/details/90413860

https://blog.csdn.net/zzg1229059735/article/details/82715741

到此这篇关于详解Java中ThreadLocal类型及简单用法的文章就介绍到这了,更多相关Java中ThreadLocal类型内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • Java实现经典游戏复杂迷宫

    这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
  • java 运行报错has been compiled by a more recent version of the Java Runtime

    java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01
  • 在java中获取List集合中最大的日期时间操作

    这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • php中浮点型(float)和整型(integer)数据类型详解

    文章分析了关于php中浮点型(float)和整型(integer)数据类型的用法区别以及在那种情况下会出现数据长度不够。 取值只能为True或者False,当其他类型转化为boolean类...2016-11-25
  • 教你怎么用Java获取国家法定节假日

    这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • 浅谈Java与C#的一些细微差别

    说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
  • c# 数据类型占用的字节数介绍

    本篇文章主要是对c#中数据类型占用的字节数进行了详细的介绍。需要的朋友可以过来参考下,希望对大家有所帮助...2020-06-25
  • Javascript类型转换的规则实例解析

    这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • java 画pdf用itext调整表格宽度、自定义各个列宽的方法

    这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • 深入理解PHP变量的值类型和引用类型

    在PHP中,大部分变量类型,如字符串,整型,浮点,数组等都是值类型的,而类和对象是引用类型,在使用的时候,需要注意这一点。看到网友在讨论PHP的&符号,要彻底理解它的用法,就有必要讨论一下变量的两种形式。PHP的变量在内存中是这样...2015-10-23
  • Java生成随机姓名、性别和年龄的实现示例

    这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
  • JQuery.Ajax()的data参数类型实例详解

    假如现在有这样一个表单,是添加元素用的。<form id='addForm' action='UserAdd.action' type='post'> <label for='uname'>用户名</label>:<input type='text' name='uname' id='uname'><br>...2015-11-24
  • java正则表达式判断前端参数修改表中另一个字段的值

    这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
  • Java使用ScriptEngine动态执行代码(附Java几种动态执行代码比较)

    这篇文章主要介绍了Java使用ScriptEngine动态执行代码,并且分享Java几种动态执行代码比较,需要的朋友可以参考下...2021-04-15
  • Java开发实现人机猜拳游戏

    这篇文章主要介绍了Java开发实现人机猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-03
  • Javascript中的数据类型之旅

    虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number、String、Boolean、Object、Udefined、Null。其中,Object属于复杂数据类型,Object 由无序的键值对组成。其余几种都属于简单数据类型。注意:变量...2015-10-21