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

相关推荐
程序员的世界你不懂31 分钟前
【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
java·前端·数据库
星空寻流年32 分钟前
设计模式第一章(建造者模式)
java·设计模式·建造者模式
lingchen19061 小时前
MATLAB的数值计算(三)曲线拟合与插值
开发语言·matlab
gb42152871 小时前
java中将租户ID包装为JSQLParser的StringValue表达式对象,JSQLParser指的是?
java·开发语言·python
一朵梨花压海棠go1 小时前
html+js实现表格本地筛选
开发语言·javascript·html·ecmascript
曾经的三心草2 小时前
Python2-工具安装使用-anaconda-jupyter-PyCharm-Matplotlib
android·java·服务器
蒋星熠2 小时前
Flutter跨平台工程实践与原理透视:从渲染引擎到高质产物
开发语言·python·算法·flutter·设计模式·性能优化·硬件工程
Metaphor6922 小时前
Java 高效处理 Word 文档:查找并替换文本的全面指南
java·经验分享·word
ChinaRainbowSea2 小时前
7. LangChain4j + 记忆缓存详细说明
java·数据库·redis·后端·缓存·langchain·ai编程
stormsha2 小时前
飞算JavaAI炫技赛电商系统商品管理模块的架构设计与实现
java·架构·鸿蒙系统