一、ThreadLocal简介

ThreadLocal是线程的局部变量,是每一个线程所单独持有的,其他线程不能对其进行访问,通常是类中的private static字段。

我们知道有时候一个对象的变量会被多个线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。

当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。

但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

二、ThreadLocal源码分析

(1)ThreadLocal方法

ThreadLocal 的几个方法: ThreadLocal 可以存储任何类型的变量对象, get返回的是一个Object对象,但是我们可以通过泛型来制定存储对象的类型。

  1. public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
  2. public void set(T value) { } //set()用来设置当前线程中变量的副本
  3. public void remove() { } //remove()用来移除当前线程中变量的副本
  4. protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的

(2)ThreadLocal实现原理

1.内部结构

Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表, 在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的。

  1. ThreadLocal.ThreadLocalMap threadLocals = null;

 ThreadLocalMap 是定义在ThreadLocal 类里的内部类,它的作用是存储线程的局部变量。ThreadLocalMap 以ThreadLocal的引用作为键,以局部变量作为值,存储在ThreadLocalMap.Entry (一种存储键值的数据结构)里。这是因为在每一个线程里面,可能存在着多个ThreadLocal变量

  1. static class ThreadLocalMap {
  2. static class Entry extends WeakReference<ThreadLocal<?>> {
  3. /** The value associated with this ThreadLocal. */
  4. Object value;
  5. Entry(ThreadLocal<?> k, Object v) {
  6. super(k);
  7. value = v;
  8. }
  9. }
  10. }

2.调用过程

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找

注意:

在进行get之前,必须先set,否则会报空指针异常;

如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

  

ThreadLocal的set(T value)方法

  1. public void set(T value) {
  2. // 获得当前线程
  3. Thread t = Thread.currentThread();
  4. // 获得当前线程的 ThreadLocalMap 引用,详细见下
  5. ThreadLocalMap map = getMap(t);
  6. // 如果不为空,则更新局部变量的值
  7. if (map != null)
  8. map.set(this, value);
  9. //如果不是第一次使用,先进行初始化
  10. else
  11. createMap(t, value);
  12. }

内部类ThreadLocalMap的set(ThreadLocal<?> key,Object value)

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. // Hash 寻址,与table数组长度减1(二进制全是1)相与,所以数组长度必须为2的次方,减小hash重复的可能性
  5. int i = key.threadLocalHashCode & (len-1);
  6. //从hash值计算出的下标开始遍历
  7. for (Entry e = tab[i];
  8. e != null;
  9. e = tab[i = nextIndex(i, len)]) {
  10. //获得该Entry的键
  11. ThreadLocal<?> k = e.get();
  12. //如果键和传过来的相同,覆盖原值,也说明,一个ThreadLocal变量只能为一个线程保存一个局部变量
  13. if (k == key) {
  14. e.value = value;
  15. return;
  16. }
  17. // 键为空,则替换该节点
  18. if (k == null) {
  19. replaceStaleEntry(key, value, i);
  20. return;
  21. }
  22. }
  23. tab[i] = new Entry(key, value);
  24. int sz = ++size;
  25. //是否需要扩容
  26. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  27. rehash();
  28. }

可以看出ThreadLocalMap 采用线性探测再散列解决Hash冲突的问题。即,如果一次Hash计算出来的数组下标被占用,即hash值重复了,则在该下标的基础上加1测试下一个下标,直到找到空值。比如说,Hash计算出来下标i为6,table[6] 已经有值了,那么就尝试table[7]是否被占用,依次类推,直到找到空值。以上,就是保存线程本地变量的方法。

TheadLocal的get()方法

  1. public T get() {
  2. //获得当前线程
  3. Thread t = Thread.currentThread();
  4. //得到当前线程的一个threadLocals 变量
  5. ThreadLocalMap map = getMap(t);
  6. if (map != null) {
  7. // 如果不为空,以当前ThreadLocal为主键获得对应的Entry
  8. ThreadLocalMap.Entry e = map.getEntry(this);
  9. if (e != null) {
  10. @SuppressWarnings("unchecked")
  11. T result = (T)e.value;
  12. return result;
  13. }
  14. }
  15. //如果值为空,则进行初始化
  16. return setInitialValue();
  17. }

ThreadLocal的setInitialValue()方法

  1. private T setInitialValue() {
  2. //获得初始默认值
  3. T value = initialValue();
  4. //得到当前线程
  5. Thread t = Thread.currentThread();
  6. // 获得该线程的ThreadLocalMap引用
  7. ThreadLocalMap map = getMap(t);
  8. //不为空则覆盖
  9. if (map != null)
  10. map.set(this, value);
  11. else
  12. //若是为空,则进行初始化,键为本ThreadLocal变量,值为默认值
  13. createMap(t, value);
  14. }
  15. // 默认初始化返回null值,这也是 下面demo 为什么需要重写该方法的原因。如果没有重写,第一次get()操作获得的线程本地变量为null,需要进行判断并手动调用set()进行初始化
  16. protected T initialValue() {
  17. return null;
  18. }

三、Demo

下面是个ThreadLocal使用的实例,两个任务共享同一个变量,并且两个任务都把该变量设置为了线程私有变量,这样,虽然两个任务都”持有“同一变量,但各自持有该变量的拷贝。因此,当一个线程修改该变量时,不会影响另一线程该变量的值。

  1. package thread.ThreadLocalTest;
  2. import java.util.Random;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * Created by StoneGeek on 2018/8/1.
  6. * 博客地址:http://www.cnblogs.com/sxkgeek
  7. */
  8. public class ThreadLocalDemo2 implements Runnable {
  9. // 一般会把 ThreadLocal 设置为static 。它只是个为线程设置局部变量的入口,多个线程只需要一个入口
  10. private static ThreadLocal<Student> localStudent = new ThreadLocal() {
  11. // 一般会重写初始化方法,一会分析源码时候会解释为什么
  12. @Override
  13. public Student initialValue() {
  14. return new Student();
  15. }
  16. };
  17. private Student student = null;
  18. @Override
  19. public void run() {
  20. String threadName = Thread.currentThread().getName();
  21. System.out.println("【" + threadName + "】:is running !");
  22. Random ramdom = new Random();
  23. //随机生成一个变量
  24. int age = ramdom.nextInt(100);
  25. System.out.println("【" + threadName + "】:set age to :" + age);
  26. // 获得线程局部变量,改变属性值
  27. Student stu = getStudent();
  28. stu.setAge(age);
  29. System.out.println("【" + threadName + "】:第一次读到的age值为 :" + stu.getAge());
  30. try {
  31. TimeUnit.SECONDS.sleep(2);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. System.out.println("【" + threadName + "】:第二次读到的age值为 :" + stu.getAge());
  36. }
  37. public Student getStudent() {
  38. student = localStudent.get();
  39. // 如果不重写初始化方法,则需要判断是否为空,然后手动为ThreadLocal赋值,否则的话会报空指针异常
  40. // if(student == null){
  41. // student = new Student();
  42. // localStudent.set(student);
  43. // }
  44. return student;
  45. }
  46. public static void main(String[] args) {
  47. ThreadLocalDemo2 ll = new ThreadLocalDemo2();
  48. Thread t1 = new Thread(ll, "线程1");
  49. Thread t2 = new Thread(ll, "线程2");
  50. t1.start();
  51. t2.start();
  52. }
  53. }
  54. console打印:
  55. 【线程2】:is running !
  56. 【线程1】:is running !
  57. 【线程1】:set age to :67
  58. 【线程2】:set age to :4
  59. 【线程1】:第一次读到的age值为 :67
  60. 【线程2】:第一次读到的age值为 :4
  61. 【线程1】:第二次读到的age值为 :67
  62. 【线程2】:第二次读到的age值为 :4

四、应用场景

最常见的ThreadLocal使用场景为

用来解决 数据库连接、Session管理等。

数据库连接

  1. Class A implements Runnable{
  2. private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
  3. public Connection initialValue() {
  4. return DriverManager.getConnection(DB_URL);
  5. }
  6. };
  7. public static Connection getConnection() {
  8. return connectionHolder.get();
  9. }
  10. }

Session管理

  1. private static final ThreadLocal threadSession = new ThreadLocal();
  2. public static Session getSession() throws InfrastructureException {
  3. Session s = (Session) threadSession.get();
  4. try {
  5. if (s == null) {
  6. s = getSessionFactory().openSession();
  7. threadSession.set(s);
  8. }
  9. } catch (HibernateException ex) {
  10. throw new InfrastructureException(ex);
  11. }
  12. return s;
  13. }

java之ThreadLocal详解的更多相关文章

  1. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  2. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  3. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  4. Java String类详解

    Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...

  5. 最新java数组的详解

    java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...

  6. JAVA IO 类库详解

    JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...

  7. 转:Java HashMap实现详解

    Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1.    HashMap概述:    HashMap是基于哈希表的M ...

  8. 淘宝JAVA中间件Diamond详解(2)-原理介绍

    淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...

  9. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

随机推荐

  1. 深入vue源码,了解vue的双向数据绑定原理

    大家都知道vue是一种MVVM开发模式,数据驱动视图的前端框架,并且内部已经实现了双向数据绑定,那么双向数据绑定是怎么实现的呢? 先手动撸一个最最最简单的双向数据绑定 <div> < ...

  2. (六十五)c#Winform自定义控件-思维导图/组织架构图(工业)

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...

  3. 我的JS文件

    {  "squadName" : "林宥嘉",  "homeTown" : "1987年7月1日(农历六月初六)",  ...

  4. Redis的实现(java)

    日常操作 public static void main(String[] args) { Jedis jedis = ); //1.开启事务 Transaction transaction = je ...

  5. Hibernate 之 @Query查询

    注解  @Query 允许在方法上使用 JPQL. 列如: @Query("select u from User u where u.name=?1 and u.department_id= ...

  6. Metasploit工具----辅助模块

    Metasploit框架(Metasploit Framework,MSF)是一个开源工具,旨在方便渗透测试,他是有Ruby程序语言编写的模板化框架,具有很好的扩展性,便于渗透测试人员开发.使用定制的 ...

  7. js中的几种继承方法

    JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一. 继承:子承父业:一个原本没有某些方法或属性的对象,统一写方法,拿到了另外一个对象的属性和方法 下面是js中的几种继承方式 1.改变this ...

  8. Java连载33-对象的创建和使用、内存分析

    一.创建一个学生类 每个学生都有学号信息,但是每一个学生的学号都是不同的,所以要访问这个学号必须先创建对象,通过对象去访问学号信息,学号信息不能直接通过“类”去访问,所以这种成员变量又被称为“实例变量 ...

  9. (一)spring 高级装配-@Profile

    1.环境与profile 示例:数据库配置 a:通过@Bean注解,通过EmbeddedDatabaseBuilder创建数据源 @Bean(destroyMethod="shutdown& ...

  10. [scikit-learn] 特征二值化

    1.首先造一个测试数据集 #coding:utf-8 import numpy import pandas as pd from sklearn.preprocessing import OneHot ...