首页 > 编程语言 >【Java 并发编程】ThreadLocal

【Java 并发编程】ThreadLocal

时间:2023-10-17 11:44:52浏览次数:48  
标签:map Java Thread ThreadLocalMap 编程 value ThreadLocal public

目录

ThreadLocal

ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。

ThreadLocal 被提到应用最多的就是 session 管理和数据库链接管理。

示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
public class ConnectionManager {
 
    private static final ThreadLocal<Connection> dbConnectionLocal = ThreadLocal.withInitial(() -> {
        try {
            return DriverManager.getConnection("https://xxx", "user", "passwd");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    });

    public Connection getConnection() {
        return dbConnectionLocal.get();
    }
}

ThreadLocal 实现线程隔离的原理

image

ThreadLocal 有一个静态内部类 ThreadLocalMap,从源码中看到 ThreadLocalMap 其实就是一个简单的 Map 结构,底层是数组,有初始化大小,也有扩容阈值大小,数组的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值。其中,ThreadLocalMap 解决 hash 冲突的方式采用的是线性探测法,如果发生冲突会继续寻找下一个空的位置。

源码:

public class ThreadLocal<T> {

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

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

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value); // key 为当前 ThreadLocal 对象的引用
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }
}

每个 Thread 中都具备一个 ThreadLocalMap,而 ThreadLocalMap 可以存储以 ThreadLocal 为 key ,Object 对象为 value 的键值对。

image

因此,如果我们在同一个线程中声明了两个 ThreadLocal 对象,Thread 内部都是使用仅有的那个 ThreadLocalMap 存放数据的,ThreadLocalMap 的 key 就是 ThreadLocal 对象,value 就是 ThreadLocal 对象调用 set 方法设置的值。

ThreadLocal 内存泄漏场景

内存泄漏的示例:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    static class LocalVariable {
        private final Long[] a = new Long[1024 * 1024];
    }
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000 * 4);
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(() -> {
                localVariable.set(new LocalVariable());
                System.out.println("use local variable" + localVariable.get());
                localVariable.remove();
            });
        }
        System.out.println("pool execute over");
    }
}

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用 remove() 方法

因此,在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,避免 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收。

标签:map,Java,Thread,ThreadLocalMap,编程,value,ThreadLocal,public
From: https://www.cnblogs.com/larry1024/p/17769329.html

相关文章

  • java serverlets使用数据源连接oracle数据库,并执行查询操作代码
    packagechap03;importjava.io.IOException;importjava.io.PrintWriter;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.PreparedStatement;importjava.sql.Statement;importjava.u......
  • 【Java 并发编程】LockSupport
    目录简介方法介绍阻塞和唤醒示例示例1示例2示例3阻塞对象blocker的作用和显式锁、隐式锁等待唤醒的区别简介java.util.concurrent.locks.LockSupport是一个工具类,提供了基本的线程阻塞和唤醒功能,它是创建锁和其他同步组件的基础工具,内部是基于sun.misc.Unsafe类实现的。Lo......
  • Javascript报错:Uncaught TypeError: $(...).slide is not a function
    检查网站的时候,发现网页出现一个报错,UncaughtTypeError:$(...).slideisnotafunction同时,平时没有问题的轮播图,也不轮播了。检查并解决步骤如下: 1.顺着错误提示点过去,发现就是slide函数无法运行。查看相关介绍,表示是jq文件进行了重复引用,且版本不同 如下图相关资料描......
  • 编程到底难在哪里?
    大家好,我是陶朱公Boy,一个认真生活,总想超越自己的程序员。前言知乎上有一个提问:编程到底难在哪里?↓↓↓今天,我们就这个话题,一起来做个讨论。首先调研一下,程序员的你,觉得编程难吗?如果觉得难,那到底难在哪里?有哪些地方,是你觉得自己在目前的工作或学习中,自己尚有欠缺的,需要花时......
  • Java IO 与 NIO:高效的输入输出操作探究
    引言输入输出(IO)是任何编程语言中的核心概念,而在Java中,IO操作更是应用程序成功运行的基石。随着计算机系统变得越来越复杂,对IO的要求也日益增加。在本文中,我们将探讨JavaIO和非阻塞IO(NIO)的重要性以及如何在Java中实现高效的输入输出操作。传统IO(阻塞IO)传统IO是大多数开发人员熟......
  • #关于编程小白的初次学习
    关于编程小白的学习之对前后端的理解及选择​据个人理解我把数据库比作一位数学出题者,而后端要做的就是解出这道数学题,并将这个答案交给前端。对于前端,后端给的答案终究还是太过枯燥无味,于是前端的任务就是把这个答案美化,让阅卷老师(用户)能看得更舒服,这样我们的分数才能高,当然这......
  • 这篇记录一下C#和java中的http request的测试
    1.在c#中,测试http的,一般做一个mock的httpContext,然后里面的request和response都使用mock的,这样设置好后,就可以测试controller2.在java中,因为是httpservelet这些,所以实例化测试创建mock的httprequest这些https://www.baeldung.com/java-httpservletrequest-mock@Testvoid......
  • java学习第一天-安装JDK,运行Hello.java
    卸载JDK删除java的安装目录删除JAVA_HOME删除path下关于java的目录cmd下输入java-version安装JDK华为云JDK下载链接,首先下载JDK下载对应版本安装JDK设置安装路径配置环境变量打开环境变量新建系统变量-->JAVA_HOME配置path变量,新建%JAVA_......
  • Java内部类进行回调的场合和范例
    大力:“内部类回调外部类的实现,是咋回事?”卫琴:“回调实质上是指一个外部类尽管实际上实现了某种功能,但是没有直接提供相应的接口,客户类可以通过这个外部类的内部类的接口来获得这种功能。而这个内部类本身并没有提供真正的实现,仅仅调用外部类的实现。可见,回调充分发挥了内部类具有访......
  • Java 开发环境配置
    转载:https://www.w3cschool.cn/java/java-environment-setup.htmlwindow系统安装java下载JDK 配置环境变量1.右击“我的电脑”→“属性”→“高级系统设置”→“高级”→“环境变量”; 在"系统变量"中设置3项属性,JAVA_HOME,PATH,CLASSPATH(大小写无所谓),若已存在则点击"......