Java的ThreadLocal

ThreadLocal

ThreadLocal 是 Java 中一个非常有用的类,它允许你创建线程局部变量。线程局部变量是指每个线程都有自己独立的变量副本,互不干扰。ThreadLocal 主要用于解决多线程环境下共享数据的线程安全性问题。

基本用法

创建 ThreadLocal 变量

复制代码
ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();

设置和获取线程局部变量:

复制代码
threadLocalVariable.set(42); // 设置线程局部变量的值
int value = threadLocalVariable.get(); // 获取线程局部变量的值

初始值:

你可以通过覆盖 ThreadLocal 的 initialValue 方法来指定线程局部变量的初始值。例如:

复制代码
ThreadLocal<Integer> threadLocalVariable = ThreadLocal.withInitial(() -> 0);

注意事项:

使用 ThreadLocal 时要小心内存泄漏,确保在不需要使用线程局部变量时及时清理。

在使用线程池时,注意线程复用可能导致线程局部变量的状态被共享。

ThreadLocal 是一个有助于在多线程应用程序中维护线程局部状态的重要工具,但它需要谨慎使用,以避免潜在的问题。确保理解其工作原理,并在需要的情况下适当使用它,可以提高多线程程序的性能和可维护性。

ThreadLocal理解

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

最多的是session管理和数据库链接管理,这里以数据访问为例帮助你理解ThreadLocal:

复制代码
class ConnectionManager {
    private static Connection connect = null;

    public static Connection openConnection() {
        if (connect == null) {
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public static void closeConnection() {
        if (connect != null)
            connect.close();
    }
}

据库管理类在单线程使用是没有任何问题的,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,多次进行closeConnection可能会导致空指针异常问题;第三,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

为了解决上述线程安全的问题,第一考虑:互斥同步

你可能会说,将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理,比如用Synchronized或者ReentrantLock互斥锁。

这里再抛出一个问题:这地方到底需不需要将connect变量进行共享?

事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。即改后的代码可以这样:

复制代码
class ConnectionManager {
    private Connection connect = null;

    public Connection openConnection() {
        if (connect == null) {
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public void closeConnection() {
        if (connect != null)
            connect.close();
    }
}

class Dao {
    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection();

        // 使用connection进行操作

        connectionManager.closeConnection();
    }
}

这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。

这时候ThreadLocal登场了那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。下面就是网上出现最多的例子:

复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

    private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
        @Override
        protected Connection initialValue() {
            try {
                return DriverManager.getConnection("", "", "");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    };

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

ThreadLocal原理

如何实现线程隔离

主要是用到了Thread对象中的一个ThreadLocalMap类型的变量threadLocals, 负责存储当前线程的关于Connection的对象, dbConnectionLocal(以上述例子中为例) 这个变量为Key, 以新建的Connection对象为Value; 这样的话, 线程第一次读取的时候如果不存在就会调用ThreadLocal的initialValue方法创建一个Connection对象并且返回;

具体关于为线程分配变量副本的代码如下:

复制代码
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap threadLocals = getMap(t);
    if (threadLocals != null) {
        ThreadLocalMap.Entry e = threadLocals.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html

相关推荐
守护者1703 分钟前
JAVA学习-练习试用Java实现“一个词频统计工具 :读取文本文件,统计并输出每个单词的频率”
java·学习
bing_15814 分钟前
Spring Boot 中ConditionalOnClass、ConditionalOnMissingBean 注解详解
java·spring boot·后端
ergdfhgerty16 分钟前
斐讯N1部署Armbian与CasaOS实现远程存储管理
java·docker
勤奋的知更鸟29 分钟前
Java性能测试工具列举
java·开发语言·测试工具
三目君33 分钟前
SpringMVC异步处理Servlet
java·spring·servlet·tomcat·mvc
用户05956611920933 分钟前
Java 基础篇必背综合知识点总结包含新技术应用及实操指南
java·后端
fie888933 分钟前
Spring MVC扩展与SSM框架整合
java·spring·mvc
不太可爱的叶某人41 分钟前
【学习笔记】深入理解Java虚拟机学习笔记——第3章 垃圾收集器与内存分配策略
java·笔记·学习
YuTaoShao41 分钟前
Java八股文——JVM「类加载篇」
java·开发语言·jvm
StackOverthink1 小时前
[特殊字符] Altair:用Python说话,让数据自己讲故事!!!
开发语言·python·其他·信息可视化