深入研究java.lang.ThreadLocal类

2023-11-05

深入研究java.lang.ThreadLocal类

一、概述

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

二、API说明

ThreadLocal()

          创建一个线程本地变量。

 T get()

          返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。

protected  T initialValue()

          返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。

   若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

void remove()

          移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

void set(T value)

          将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。

在程序中一般都重写initialValue方法,以给定一个特定的初始值。

三、典型实例

1、Hiberante的Session 工具类HibernateUtil

这个类是Hibernate官方文档中HibernateUtil类,用于session管理。

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定义SessionFactory
 
    static {
        try {
            // 通过默认配置文件hibernate.cfg.xml创建SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失败!", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    //创建线程局部变量session,用来保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
 
    /**
     * 获取当前线程中的Session
     * @return Session
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // 如果Session还没有打开,则新开一个Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //将新开的Session保存到线程局部变量中
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException {
        //获取线程局部变量,并强制转换为Session类型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在这个类中,由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此,对session做了判断,如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键,这也是“public static final ThreadLocal session = new ThreadLocal()”所创建对象session能强制转换为Hibernate Session对象的原因。

2、另外一个实例

创建一个Bean,通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。  

​
public class Student {
    private int age = 0;   //年龄
 
    public int getAge() {
        return this.age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}

​
public class ThreadLocalDemo implements Runnable {
    //创建线程局部变量studentLocal,在后面你会发现用来保存Student对象
    private final static ThreadLocal studentLocal = new ThreadLocal();

 

    public static void main(String[] agrs) {
        ThreadLocalDemo td = new ThreadLocalDemo();
        Thread t1 = new Thread(td, "a");
        Thread t2 = new Thread(td, "b");
        t1.start();
        t2.start();
    }

 

    public void run() {
        accessStudent();
    }

 

    /**
     * 示例业务方法,用来测试
     */
    public void accessStudent() {
        //获取当前线程的名字
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");

        //产生一个随机数并打印
        Random random = new Random();
        int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);

        //获取一个Student对象,并将随机数年龄插入到对象属性中
        Student student = getStudent();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
        try {
            Thread.sleep(500);
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }

 

    protected Student getStudent() {
        //获取本地线程变量并强制转换为Student类型
        Student student = (Student) studentLocal.get();
        //线程首次执行此方法的时候,studentLocal.get()肯定为null
        if (student == null) {
            //创建一个Student对象,并保存到本地线程变量studentLocal中
            student = new Student();
            studentLocal.set(student);
        }
        return student;
    }
}

运行结果:

a is running! 
thread a set age to:76 
b is running! 
thread b set age to:27 
thread a first read age is:76 
thread b first read age is:27 
thread a second read age is:76 
thread b second read age is:27 

可以看到a、b两个线程age在不同时刻打印的值是完全相同的。这个程序通过妙用ThreadLocal,既实现多线程并发,游兼顾数据的安全性。

 

四、总结

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。 

五、ThreadLocal使用的一般步骤

1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。

2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。

3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入研究java.lang.ThreadLocal类 的相关文章

  • 等待和通知机制(wait和notify)

    1 等待和通知机制的实现 wait 方法 wait 是 Object 类的方法 它的作用是使当前执行wait方法的线程进行等待 该方法将当前线程置入 预执行队列 中 并在 wait 所在的代码行处停止执行 直到接到通知或者被中断才能继续执行
  • 多线程下载

    原理 服务器cpu分配给每条线程的时间片是相同的 服务器带宽平均分配给每个线程 所以客户端开启的线程越多就能抢占到更多的服务器资源 用java实现 public class NultiDownload static String path
  • 线程中捕获异常

    总结 正常线程抛出异常时 在外部是捕捉不到的 当此类异常跑抛出时 线程就会终结 而对于主线程和其他线程完全不受影响 且完全感知不到某个线程抛出的异常 也是说完全无法catch到这个异常 解决方案 为线程添加未捕获异常处理器 Uncaught
  • 看一眼就会Java多线程!(如何全面理解多线程)

    基本概念 程序 进程 线程 程序 program 是为了完成特殊任务 用某种语言编写的一组指令的集合 即指 一段静态的代码 静态对象 进程 process 是程序的一次执行过程 或是 正在运行的一个程序 是一个动态的过程 有它自身的产生 存
  • Java多线程 - 线程通信(线程协作)

    生产者消费者模式 举个例子 我作为消费者去肯德基买鸡块吃 正常情况下 如果还有鸡块的话就直接卖给我了 如果没有的话 前台就会通知后面的大厨进行制作 那么大厨就相当于是生产者 大厨做好之后会给到前台 然后前台通知我 消费者 来取餐 在线程中
  • Java学习笔记-锁

    Java学习笔记 锁 Lock 锁 从JDK5 0开始 Java提供了更强大的线程同步机制 通过显式定义同步锁对象来实现同步 同步锁使用Lock对象充当 java util concurrent locks Lock接口是个控制多线程对共享
  • Java中如何捕获其他线程抛出的异常

    如Java中另一个线程抛出的异常 可以使用公共静态接口Thread UncaughtExceptionHandler完成 Thread UncaughtExceptionHandler是当线程因未捕获的异常而突然终止时调用的处理程序接口 当
  • UncaughtExceptionHandler异常处理机制

    解释 UncaughtExceptionHandler类是java1 5里新增的 Thread类里面的一个函数式接口类的 类名意思为 未捕获的异常处理 该类的注释接口意思 接口处理器时调用线程突然终止 由于未捕获到异常 当一个线程要终止由于
  • Java 并发工具包 java.util.concurrent 用户指南

    http blog csdn net defonds article details 44021605 comments 译序 本指南根据 Jakob Jenkov 最新博客翻译 请随时关注博客更新 http tutorials jenko
  • synchronized方法和代码块

    1 同步 由于多线程并发存在数据不安全问题 为了保证数据的安全性需要一些特殊的手段来维持 数据不安全主要是针对修改来说的 如果一个数据只能读不能修改几乎不会产生什么安全问题 只有修改数据的时候容易产生一些差错导致多线程并发造成数据不安全 从
  • Java多线程技术详解(全都是细节!)

    多线程启动 线程有两种启动方式 实现Runnable接口 继承Thread类并重写run 方法 1 Thread 与 Runnable Runnable接口表示线程要执行的任务 当Runnable中的run 方法执行时 表示线程在激活状态
  • 【2021最新版】Java多线程&并发面试题总结(108道题含答案解析)

    文章目录 JAVA并发知识库 1 Java中实现多线程有几种方法 2 继承Thread类 3 实现Runnable接口 4 ExecutorService Callable Future有返回值线程 5 基于线程池的方式 6 4 种线程池
  • Java并发总结之Java内存模型

    本文主要参考 深入理解Java虚拟机 和 Java并发编程的艺术 对Java内存模型进行简单总结 一 CPU和缓存一致性 1 CPU高速缓存 为了解决CPU处理速度和内存处理速度不对等的问题 就是在CPU和内存之间增加高速缓存 当程序在运行
  • java多线程总结:原理结合源码详细讲解 - 简单实用

    执行策略 线程执行的方式 串行执行 比如 医院给病人看病的时候 可以让所有的病人都拍成一个队形 让一个医生统一的看病 医生 线程 病人看病 任务 这种一个医生给一群站好队形的病人看病 映射到java就相当于 单线程串行执行任务 映射到我们j
  • Java-多线程-给线程命名

    Java 多线程 给线程命名 在Java中 通过继承Thread创建的线程 有以下两种方式可以给线程命名 通过构造器命名 因为线程类继承自Thread类 所有也继承了Thread的name属性 可以通过super的方法调用父类构造器 将na
  • Java多线程:解决高并发环境下数据插入重复问题

    1 背景描述 应用框架 Spring SpringMVC Hibernate 数据库 Oracle11g 一家文学网站向我系统推多线程低并发推送数据 我这边观察日志和数据库 发现有一个作者被存储了2次到数据库中 按照程序的编写逻辑 重复的数
  • java定时器Timer的使用

    在JDK库中Timer类主要负责计划任务的功能 也就是在指定的时间开始执行某一个任务 Timer类的主要作用就是设置计划任务 但是封装任务的类的是TimerTask类 下面展示几个例子 在指定的日期执行一次某一任务 import java
  • 多线程太可怕了

    今天发现了一个多线程引起的bug 然后进一步体会到 这东西太容易出问题了 首先要说明的是 出问题的代码可不是一般人写的 是由一个叫EPAM systems的世界知名外包公司的人写的 这些java程序员个个经验丰富 心高气傲 貌似base在乌
  • 多线程案例:银行取钱

    不安全取钱 两个人去银行取钱 账户 银行取钱 给账户上锁 public class UnsafeBank public static void main String args 账户 Account3 account new Account
  • 多线程案例:购买车票

    购票案例 多线程同步 多线程的并发执行虽然可以提高程序的效率 但是 当多个线程去访问同一个资源时 也会引发一些安全问题 并发 同一个对象被多个线程同时操作 处理多线程问题时 多个线程访问同一个对象 并且某些线程还想修改这个对象 这时候我们就

随机推荐

  • 一场时代的挑战与转变

    在我们的社会中 有一种现象引起了人们广泛的关注 那就是 年轻人存款 的问题 据最近的一项调查显示 大概五分之一的年轻人存款在一万元以内 10万元存款是一个 坎 存款超过10万就会超过53 7 的人 这样的数据 无疑引发了人们对于年轻人与存款
  • QT之动态库的创建、及测试代码编写

    1 新建文件和目录 在项目中选择library 右侧选择C library 如下图所示 2 按choose后 出现下面如图所示 添加库工程名字 3 选择共享库 并最好修改类名 头文件 源文件的名称 如下图所示 4 选择编译器 这里需要跟需要
  • 正则表达式之匹配字符串双引号

    字符串中可以出现转义的双引号 那么一般的正则表达式就不行了 譬如 无法正确匹配 word1 word2 word3 其结果是 word2 这里我用非获取匹配 则结果是 word2 word3 表示贪婪策略 非贪婪 pattern 非获取匹配
  • 最新Navicat Premium 16 激活中文版 适用于win和mac版

    Navicat Premium 16这是十分知名且专业的数据库开发管理工具 利用它不仅为用户提供完善的工具 可以轻松完美的帮助用户构建 管理和维护您的数据库 还与市面上主流的云数据库兼容 从而可以很好的满足不同用户们的各种使用需求 同时就算
  • element el-table 设置行高

  • Python从零开始学大概需要多久时间才能达到自主接单赚钱水平?

    我能也是学Python将近六七年的时间了 就来给大家讲讲Python吧 Python作为现在行业最受欢迎的编程语言 也是一门相对于其他的编程语言 Java C C 要更加简单 狗头保命 这是有目共睹的 那么因为Python的简单 所以现在学
  • router的使用

    路由和线路 路由router 表示当前项目全局的路由实例对象 跳转方法 push replace go back 线路route 表示当前路由页面的信息对象 获取动态路由的参数 params router跳转的两种方式 js跳转叫 编程式跳
  • 如何搭建VPN?

    搭建VPN的方法可以分为两个主要步骤 设置服务器和配置客户端 下面是一般的步骤 设置服务器 1 选择适合你需求的服务器 你可以租用云服务器 自行搭建服务器或者使用第三方VPN服务提供商 2 安装操作系统并进行基本配置 常用的操作系统有Lin
  • 如何安装黑苹果双系统

    title 黑苹果Win10双系统安装教程 小白也能秒掌握 装机装了一天 看了很多教程 踩了无数坑 终于成功了 写一篇教程防止后人踩坑吧 关于黑苹果 折腾过的人应该不陌生 自从苹果采用 Intel 的处理器 被解锁后可以安装在 Intel
  • 【前端兼容性】常见的浏览器兼容问题及解决方案

    常见浏览器兼容 前言 一 常见浏览器内核 1 Trident内核 2 Gecko内核 3 Blink内核 4 Webkit内核 5 Presto内核 已废弃 二 常见兼容性问题 1 不同浏览器的默认margin和padding不一致 2 图
  • PyQty5—第四课:GUI小程序界面设计(附完整代码)

    在第一节课中我们已经学会了PyQty5的安装 以及配置好了两个环境 如果还没有看到的小伙伴可以前去考古 点我 今天我们将会继续学习PyQty5的设计界面的认识 也会带领大家一步一步设计出一个小GUI作品 首先我们创建一个py文件 然后右击
  • OpenGL学习日记-2015.3.13——多实例渲染

    实例化 instancing 或者多实例渲染 instancd rendering 是一种连续执行多条相同渲染命令的方法 并且每个命令的所产生的渲染结果都会有轻微的差异 是一种非常有效的 实用少量api调用来渲染大量几何体的方法 OpenG
  • activiti在运行时报错:couldn‘t find a variable type that is able to serialize XXX

    activiti在启动流程实例的时候报错信息如下 Exception in thread main org activiti engine ActivitiException couldn t find a variable type th
  • 【精品源码】C#20个经典小游戏集合!

    大家好 这里是小伙整理的C 20个经典小游戏集合系列 需要的可自取下载包 C 20个经典小游戏集合项目包含内容 小游戏源码目录如下 01 21点小游戏 02 百变方块小游戏 03 打字小游戏 04 单机坦克小游戏 05 对对碰小游戏 06
  • pybind11学习

    本文主要记录官方文档中 FUNCTIONS 一章的学习笔记 对于C 函数的Python绑定 在前面的学习中已经有所涉及了 详见 pybind11学习 迈出第一步 本文主要是记录一些更加深入的知识 文章目录 1 返回值策略 2 调用策略 2
  • vue中,实现锚点定位及跳转(url不发生变化)

    直接上代码 lt div class footer click returnTop gt methods returnTop function document querySelector header scrollIntoView tru
  • 大学生数学建模优秀论文发表

    大学生数学建模优秀论文篇1 浅谈大学生数学建模的意义 摘 要 本文重点分析了数学建模对当前数学教育教学改革的现实意义 探讨了数学建模对学生应用数学能力的培养 阐述了计算机在数学建模竞赛中的作用和地位 最后介绍了数学建模对数学教学改革的启示意
  • 微信小程序-weUI组件库

    微信小程序的开发过程中 常常会出现很多重复性的功能翻来覆去地使用 那么直接用一套封装好的组件库 就能大大提升开发速度 微信小程序的UI组件库有很多 可以参考下面这个内容 微信小程序UI组件库合集 微信开放社区 qq com https de
  • 英伟达GPU驱动和CUDA的版本对应关系

    CUDA Toolkit Toolkit Driver Version Linux x86 64 Driver Version Windows x86 64 Driver Version CUDA 11 6 GA gt 510 39 01
  • 深入研究java.lang.ThreadLocal类

    深入研究java lang ThreadLocal类 一 概述 ThreadLocal是什么呢 其实ThreadLocal并非是一个线程的本地实现版本 它并不是一个Thread 而是threadlocalvariable 线程局部变量 也许