IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 深耕Java多线程 - ThreadLocal的作用? 有哪些使用场景? -> 正文阅读

[Java知识库]深耕Java多线程 - ThreadLocal的作用? 有哪些使用场景?

问题:

1、ThreadLocal 是什么?有哪些使用场景?
2、ThreadLocal的设计理念与作用?
3、什么叫线程局部变量?

1. ThreadLocal是什么?

在Java的多线程并发执行过程中,为了保证多个线程对变量的安全访问,可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时被另一个线程修改的现象。

ThreadLocal类通常被翻译为“线程本地变量”类或者“线程局部变量”类。

2. ThreadLocal基本作用?

ThreadLocal位于JDK的java.lang核心包中。如果程序创建了一个ThreadLocal实例,那么在访问这个变量的值时,每个线程都会拥有一个独立的、自己的本地值。“线程本地变量”可以看成专属于线程的变量,不受其他线程干扰,保存着线程的专属数据。当线程结束后,每个线程所拥有的那个本地值会被释放。在多线程并发操作“线程本地变量”的时候,线程各自操作的是自己的本地值,从而规避了线程安全问题。

Java程序可以调用ThreadLocal的成员方法进行本地值的操作,具体的成员方法:

(1) set(T value):设置当前线程在“线程本地变量”实例中绑定的本地值;
(2) T get():获取当前线程在“线程本地变量”实例中绑定的本地值;
(3) remove():移除当前线程在“线程本地变量”实例中绑定的本地值;

下面的例子通过ThreadLocal的成员方法进行“线程本地变量”中线程本地值的设置、获取和移除,具体的代码如下:

@Data
public class Person {
    private int age = 10;
}
public class TestThreadLocal {
    private static final ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 提交5个任务,使用5个线程
        for(int i=0;i<5;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    // 获取threadLocal中当前线程绑定的值
                    if(threadLocal.get()==null){
                        // 设置threadLocal中当前线程绑定的值
                        threadLocal.set(new Person());
                    }
                    System.out.println("初始的本地值:"+threadLocal.get());
                    // 每个线程执行10次
                    for(int i=0;i<10;i++){
                        Person foo = threadLocal.get();
                        foo.setAge(foo.getAge()+1);
                        // 睡眠1s
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("累加10次之后的本地值:"+threadLocal.get());
                    // 删除threadLocal中当前线程所绑定的值,这点对于线程池中的线程特别重要
                    threadLocal.remove();
                }
            });
        }
    }
}

执行结果:

初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)

在“线程本地变量”(threadLocal)l中,每一个线程都绑定了一个独立的值(Person对象),这些值对象是线程的私有财产,可以理解为线程的本地值,线程的每一次操作都是在自己的同一个本地值上进行的。

如果线程尚未在本地变量(如threadLocal)中绑定一个值,直接通过调用get()方法获取本地值会获取到一个空值,此时可以通过调用set()方法设置一个值作为初始值,具体的代码如下:

// 获取threadLocal中当前线程绑定的值
if(threadLocal.get()==null){
    // 设置threadLocal中当前线程绑定的值
    threadLocal.set(new Person());
}

在当前线程尚未绑定值时,如果希望从线程本地变量获取到初始值,而且不想采用以上的“判空后设值”这种相对烦琐的方式,可以调用ThreadLocal.withInitial(…)静态工厂方法,在定义ThreadLocal对象时设置一个获取初始值的回调函数,具体的代码如下:

private static final ThreadLocal<Person> threadLocal = ThreadLocal.withInitial(()->new Person());

以上代码并没有使用new ThreadLocal<Person>()构造一个ThreadLocal对象,而是调用withInitial(…)工厂方法创建一个ThreadLocal对象,并传递了一个获取初始值的Lambda回调函数。在线程尚未绑定值而直接从“线程本地变量”获取值时,将会取得回调函数被调用之后所返回的值。

3. ThreadLocal的应用场景?

ThreadLocal是解决线程安全问题的一个较好的方案,它通过为每个线程提供一个独立的本地值去解决并发访问的冲突问题。在很多情况下,使用ThreadLocal比直接使用同步机制(如synchronized)解决线程安全问题更简单、更方便,且结果程序拥有更高的并发性。

ThreadLocal的使用场景大致可以分为以下两类:

1. 线程隔离

ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。

ThreadLocal在线程隔离的常用案例为:可以为每个线程绑定一个用户会话信息、数据库连接、HTTP请求等,这样一个线程所有调用到的处理函数都可以非常方便地访问这些资源。常见的ThreadLocal使用场景为数据库连接独享、Session数据管理等。

在“线程隔离”场景中,使用ThreadLocal的典型案例为:可以为每个线程绑定一个数据库连接,使得这个数据库连接为线程所独享,从而避免数据库连接被混用而导致操作异常问题。

下面的代码来自Hibernate,代码中通过ThreadLocal进行数据库连接(Session)的“线程本地化”存储,主要的代码如下:

private static final ThreadLocal threadSession= new ThreadLocal();
public static Session getSession(){
    Session s = (Session)threadSession.get();
    if(s==null){
        getSessionFactory().openSession();
        threadSession.set(s);
    }
}

Hibernate对数据库连接进行了封装,一个Session代表一个数据库连接。通过以上代码可以看到,在Hibernate的getSession()方法中,首先判断当前线程中有没有放进去Session,如果还没有,那么通过sessionFactory().openSession()来创建一个Session,再将Session设置到ThreadLocal变量中,这个Session相当于线程的私有变量,而不是所有线程共用的,显然其他线程中是取不到这个Session的。

一般来说,完成数据库操作之后程序会将Session关闭,从而节省数据库连接资源。如果Session的使用方式为共享而不是独占,在这种情况下,Session是多线程共享使用的,如果某个线程使用完成之后直接将Session关闭,其他线程在操作Session时就会报错。所以Hibernate通过ThreadLocal非常简单地实现了数据库连接的安全使用。

2. 跨函数传递

数据通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。

由于ThreadLocal的特性,同一线程在某些地方进行设置,在随后的任意地方都可以获取到。线程执行过程中所执行到的函数都能读写ThreadLocal变量的线程本地值,从而可以方便地实现跨函数的数据传递。使用ThreadLocal保存函数之间需要传递的数据,在需要的地方直接获取,也能避免通过参数传递数据带来的高耦合。

在“跨函数传递数据”场景中使用ThreadLocal的典型案例为:可以为每个线程绑定一个Session(用户会话)信息,这样一个线程所有调用到的代码都可以非常方便地访问这个本地会话,而不需要通过参数传递。

ThreadLocal在“跨函数数据传递”场景的典型应用有很多:

(1)用来传递请求过程中的用户ID。

(2)用来传递请求过程中的用户会话(Session)。

(3)用来传递HTTP的用户请求实例HttpRequest。

(4)其他需要在函数之间频繁传递的数据。

public class Test {
    // session id,线程本地变量
    private static final ThreadLocal<String> sidLocal= new ThreadLocal("sidLocal");

    // 用户信息,线程本地变量
    private static final ThreadLocal<UserDTO> sessionUserLocal = new ThreadLocal<>("sessionUserLocal");

    // session,线程本地变量
    private static final ThreadLocal<HttpSession> sessionLocal = new ThreadLocal<>("sessionLocal");

    // 保存session在线程本地变量中
    public static Session setSession(HttpSession httpSession){
        sessionLocal.set(httpSession);
    }

    // 取得绑定在线程本地变量中的session
    public static HttpSession getSession(){
        HttpSession session = sessionLocal.get();
        Assert.notNull(session,"session 未设置");
        return session;
    }
    
    // 省略....
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-10 22:17:51  更:2022-03-10 22:18:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:57:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码