`

ThreadLocal-分析-总结

 
阅读更多

==============转载: 讨论帖 http://www.iteye.com/topic/777716==================

 

ThreadLocal<T>类在Spring,Hibernate等框架中起到了很大的作用,对于其工作原理,很多网上的文章分析的不够彻底,甚至有些误解。

 

首先,为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类(内部类)

1.ThreadLocalMap

2.Thread

可能有人会觉得Thread与ThreadLocal有什么关系,其实真正的奥秘就在Thread类中的一行:

 

Java代码 复制代码  收藏代码
  1. ThreadLocal.ThreadLocalMap threadLocals =  null ;  
ThreadLocal.ThreadLocalMap threadLocals = null;

 

 其中ThreadLocalMap的定义是在ThreadLocal类中,真正的引用却是在Thread类中

 

那么ThreadLocalMap究竟是什么呢?

 

可以看到这个类应该是一个Map,JDK的解释是

 

 写道
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values
 

 

接下来的重点是ThreadLocalMap中用于存储数据的entry

 

Java代码 复制代码  收藏代码
  1. static   class  Entry  extends  WeakReference<ThreadLocal> {   
  2.              /** The value associated with this ThreadLocal. */   
  3.             Object value;   
  4.   
  5.             Entry(ThreadLocal k, Object v) {   
  6.                  super (k);   
  7.                 value = v;   
  8.             }   
  9.         }  
static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

 

 从中我们可以发现这个Map的key是ThreadLocal变量,value为用户的值,并不是网上大多数的列子key是线程的名字或者标识

 

到这里,我们就可以理解ThreadLocal究竟是如何工作的了

 

1.Thread类中有一个成员变量叫做ThreadLocalMap,它是一个Map,他的Key是ThreadLocal类

2.每个线程拥有自己的申明为ThreadLocal类型的变量,所以这个类的名字叫'ThreadLocal':线程自己的(变量)

3.此变量生命周期是由该线程决定的,开始于第一次初始(get或者set方法)

4.由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非共享或者拷贝

 

Java代码 复制代码  收藏代码
  1. /**  
  2.  * @author mxdba  
  3.  *  
  4.  */   
  5. public   class  ThreadLocalSample {   
  6.   
  7.      public   static   void  main(String[] args) {   
  8.         ThreadTest test1 =  new  ThreadTest( 10 );   
  9.         ThreadTest test2 =  new  ThreadTest( 20 );   
  10.         test1.start();   
  11.         test2.start();   
  12.     }   
  13.   
  14. }   
  15.   
  16. /**  
  17.  * 此线程有两个ThreadLocal变量,但是由于ThreadLocal是延迟初始的,  
  18.  * 所以在debug时可以看到线程名为“线程20”的线程的ThreadLocalMap中没有thLcal2这个entry  
  19.  * @author mxdba  
  20.  *   
  21.  */   
  22. class  ThreadTest  extends  Thread {   
  23.        
  24.      public   static  ThreadLocal<Integer> thLocal =  new  ThreadLocal<Integer>();   
  25.      public   static  ThreadLocal<String> thLocal2 =  new  ThreadLocal<String>();   
  26.        
  27.      public  Integer num;   
  28.        
  29.        
  30.        
  31.      public  ThreadTest(Integer num) {   
  32.          super ( "线程"  + num);   
  33.          this .num = num;   
  34.     }   
  35.   
  36.      @Override   
  37.      public   void  run() {   
  38.         Integer n = thLocal.get();   
  39.          if (num !=  20 ) {   
  40.             String s = thLocal2.get();   
  41.         }   
  42.                
  43.          if (n ==  null ) {   
  44.             thLocal.set(num);   
  45.         }   
  46.         System.out.println(thLocal.get());   
  47.     }   
  48.        
  49. }  
/**
 * @author mxdba
 *
 */
public class ThreadLocalSample {

	public static void main(String[] args) {
		ThreadTest test1 = new ThreadTest(10);
		ThreadTest test2 = new ThreadTest(20);
		test1.start();
		test2.start();
	}

}

/**
 * 此线程有两个ThreadLocal变量,但是由于ThreadLocal是延迟初始的,
 * 所以在debug时可以看到线程名为“线程20”的线程的ThreadLocalMap中没有thLcal2这个entry
 * @author mxdba
 * 
 */
class ThreadTest extends Thread {
	
	public static ThreadLocal<Integer> thLocal = new ThreadLocal<Integer>();
	public static ThreadLocal<String> thLocal2 = new ThreadLocal<String>();
	
	public Integer num;
	
	
	
	public ThreadTest(Integer num) {
		super("线程" + num);
		this.num = num;
	}

	@Override
	public void run() {
		Integer n = thLocal.get();
		if(num != 20) {
			String s = thLocal2.get();
		}
			
		if(n == null) {
			thLocal.set(num);
		}
		System.out.println(thLocal.get());
	}
	
}

 

 

接下来分析一下源码,就更加清楚了

 

关键方法代码 复制代码  收藏代码
  1. /**   
  2.  * 关键方法,返回当前Thread的ThreadLocalMap   
  3.  * [[[每个Thread返回各自的ThreadLocalMap,所以各个线程中的ThreadLocal均为独立的]]]   
  4.  */   
  5. ThreadLocalMap getMap(Thread t) {   
  6.         return t.threadLocals;   
  7.     }  
/**
 * 关键方法,返回当前Thread的ThreadLocalMap
 * [[[每个Thread返回各自的ThreadLocalMap,所以各个线程中的ThreadLocal均为独立的]]]
 */
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 

 

 

 

Threadlocal的get方法代码 复制代码  收藏代码
  1. public T get() {   
  2.         Thread t = Thread.currentThread();   
  3.         /**   
  4.          * 得到当前线程的ThreadLocalMap   
  5.          */   
  6.         ThreadLocalMap map = getMap(t);   
  7.         if (map != null) {   
  8.             /**   
  9.              * 在此线程的ThreadLocalMap中查找key为当前ThreadLocal对象的entry   
  10.              */   
  11.             ThreadLocalMap.Entry e = map.getEntry(this);   
  12.             if (e != null)   
  13.                 return (T)e.value;   
  14.         }   
  15.         return setInitialValue();   
  16.     }  
public T get() {
        Thread t = Thread.currentThread();
        /**
         * 得到当前线程的ThreadLocalMap
         */
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            /**
             * 在此线程的ThreadLocalMap中查找key为当前ThreadLocal对象的entry
             */
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
 

 

 

 

初始化方法代码 复制代码  收藏代码
  1. private T setInitialValue() {   
  2.         /**   
  3.          * 默认返回null,这个方法为protected可以继承   
  4.          */   
  5.         T value = initialValue();   
  6.         Thread t = Thread.currentThread();   
  7.         ThreadLocalMap map = getMap(t);   
  8.         if (map != null)   
  9.             map.set(this, value);   
  10.         else   
  11.             /**   
  12.              * 初次创建   
  13.              */   
  14.             createMap(t, value);   
  15.         return value;   
  16.     }  
private T setInitialValue() {
        /**
         * 默认返回null,这个方法为protected可以继承
         */
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            /**
             * 初次创建
             */
            createMap(t, value);
        return value;
    }
 

 

 

Java代码 复制代码  收藏代码
  1. /**  
  2.  * 给当前thread初始ThreadlocalMap  
  3.  */   
  4. void  createMap(Thread t, T firstValue) {   
  5.         t.threadLocals =  new  ThreadLocalMap( this , firstValue);   
  6.     }  
/**
 * 给当前thread初始ThreadlocalMap
 */
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 

 

 

通过上边的分析,我们发现,ThreadLocal类的使用虽然是用来解决多线程的问题的,但是还是有很明显的针对性

1.最明显的,ThreadLoacl变量的活动范围为某线程,并且我的理解是该线程“专有的,独自霸占”,对该变量的所有操作均有该线程完成! 也就是说,ThreadLocal不是用来解决共享,竞争问题的。典型的应用莫过于Spring,Hibernate等框架中对于多线程的处理了。

下面来看一个 hibernate 中典型的 ThreadLocal 的应用:

 

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

 这段代码,每个线程有自己的ThreadLocalMap,每个ThreadLocalMap中根据需要初始加载threadSession,这样的好处就是介于singleton与prototype之间,应用singleton无法解决线程,应用prototype开销又太大,有了ThreadLocal之后就好了,对于需要线程“霸占”的变量用ThreadLocal,而该类实例的方法均可以共享。

 

2.关于内存泄漏:

虽然ThreadLocalMap已经使用了weakReference,但是还是建议能够显示的使用remove方法。

 

 

==============转载: 讨论帖 http://ari.iteye.com/blog/757478==================

 

一、ThreadLocal概述

       学习JDK中的类,首先看下JDK API对此类的描述,描述如下:

JDK API 写道
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

    API表达了下面几种观点:

1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。

2、ThreadLocal 在类中通常定义为静态类变量。

3、每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。

 

    既然定义为类变量,为何为每个线程维护一个副本(姑且成为‘拷贝’容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。

 

   ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’ ,synchronized顺序执行是‘时间换取空间’

 

二、ThreadLocal方法介绍

 

 

 T get ()
          返回此线程局部变量的当前线程副本中的值。
protected  T initialValue ()
          返回此线程局部变量的当前线程的“初始值”。
 void remove ()
          移除此线程局部变量当前线程的值。
 void set (T  value)
          将此线程局部变量的当前线程副本中的值设置为指定值。

 

三、深入源码

    ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。

    当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量,代码如下:

 

Get方法代码 复制代码  收藏代码
  1. public T get() {   
  2.        Thread t = Thread.currentThread();   
  3.        ThreadLocalMap map = getMap(t);   
  4.        if (map != null) {   
  5.            ThreadLocalMap.Entry e = map.getEntry(this);   
  6.            if (e != null)   
  7.                return (T)e.value;   
  8.        }   
  9.        return setInitialValue();   
  10. }  
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
 }

    调用get方法如果此Map不存在首先初始化,创建此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,我们可以覆盖此方法,在首次调用时初始化一个适当的值。setInitialValue代码如下:

Java代码 复制代码  收藏代码
  1. private  T setInitialValue() {   
  2.     T value = initialValue();   
  3.     Thread t = Thread.currentThread();   
  4.     ThreadLocalMap map = getMap(t);   
  5.      if  (map !=  null )   
  6.         map.set( this , value);   
  7.      else   
  8.         createMap(t, value);   
  9.      return  value;   
  10. }  
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

 

    set方法相对比较简单如果理解以上俩个方法,获取当前线程的引用,从map中获取该线程对应的map,如果map存在更新缓存值,否则创建并存储,代码如下:

Java代码 复制代码  收藏代码
  1. public   void  set(T value) {   
  2.     Thread t = Thread.currentThread();   
  3.     ThreadLocalMap map = getMap(t);   
  4.      if  (map !=  null )   
  5.         map.set( this , value);   
  6.      else   
  7.         createMap(t, value);   
  8. }  
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 

    对于ThreadLocal在何处存储变量副本,我们看getMap方法:获取的是当前线程的ThreadLocal类型的threadLocals属性。显然变量副本存储在每一个线程中。

 

Java代码 复制代码  收藏代码
  1. /**  
  2.  * 获取线程的ThreadLocalMap 属性实例  
  3.  */   
  4. ThreadLocalMap getMap(Thread t) {   
  5.          return  t.threadLocals;   
  6.   }  
/**
 * 获取线程的ThreadLocalMap 属性实例
 */
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
  }

 

    上面我们知道变量副本存放于何处,这里我们简单说下如何被java的垃圾收集机制收集,当我们不在使用是调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操作,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。

 

 四、ThreadLocal应用示例

 

      在我的另一篇文章,对ThreadLocal的使用做了一个实例,此示例也可以用作生产环境,请参见:http://ari.iteye.com/blog/757641

 

 

分享到:
评论

相关推荐

    ThreadLocal 内存泄露的实例分析1

    总结:`ThreadLocal` 是一个强大的工具,但在使用时必须谨慎,尤其是在多线程环境中,如 Tomcat 这样的 Web 容器。如果不正确地管理 `ThreadLocal` 的生命周期,可能导致类加载器级别的内存泄漏。理解 `ThreadLocal`...

    正确理解ThreadLocal.pdf

    3. **日志记录**:在多线程环境中,`ThreadLocal`可以用于维护每个线程的日志上下文,如线程ID、用户名等,便于日志信息的记录和分析。 4. **性能优化**:在某些计算密集型应用中,`ThreadLocal`可以用于缓存线程...

    理解threadlocal

    通过以上分析,我们可以看到`ThreadLocal`为多线程环境下的编程提供了一种简洁优雅的解决方案。它不仅能够避免复杂的同步问题,还能有效地管理线程间的变量隔离。理解`ThreadLocal`的工作原理及其使用方法,可以帮助...

    简单分析Java线程编程中ThreadLocal类的使用共

    总结来说,Java线程编程中的ThreadLocal类提供了一种高效、便捷的方式来实现线程间的隔离数据,但同时也需要开发者注意其潜在的内存泄漏风险。正确理解和使用ThreadLocal,能够帮助我们编写出更加健壮、高效的并发...

    Java并发编程学习之ThreadLocal源码详析

    ThreadLocal的实现原理可以总结为:每个线程都有一个自己的ThreadLocalMap,用于存放线程的本地变量;ThreadLocal的set、get、remove方法都是基于ThreadLocalMap的操作。 ThreadLocal的优点是可以解决多线程访问...

    入研究java.lang.ThreadLocal类.docx

    **分析**: - 使用 `ThreadLocal&lt;Session&gt;` 确保每个线程都有独立的 Session 实例。 - 通过 `initialValue()` 方法初始化每个线程的 Session。 - `currentSession()` 方法确保每次调用都能获取到当前线程的 Session ...

    一次使用Eclipse Memory Analyzer分析Tomcat内存溢出

    #### 四、总结 通过本次使用 Eclipse Memory Analyzer (MAT) 分析 Tomcat 内存溢出的过程,我们可以得出以下结论: - 内存管理对于 Java 应用程序至关重要。 - 遇到内存溢出或泄露问题时,MAT 是一款非常强大的...

    mat java 分析 文件 dump

    进一步分析显示,“java.lang.ThreadLocal$ThreadLocalMap$Entry[]”可能是问题的根源。在这种情况下,MAT提供了指向具体问题的线索,即通过查看Leak Suspects Problem Suspect1 Details页面来获取更多信息。这里...

    并发编程总结.xmind

    java并发编程总结,包括多线程安全机制分析总结,Unsafe源码分析总结,并发工具类总结,ThreadLocal原理和使用,Fork/Join框架使用总结,同步容器和并发容器源码分析

    2019年一线互联网公司Java高级面试题总结

    - 使用工具进行性能分析,如JVisualVM、JProfiler等。 - **效果**: - 提升应用程序运行速度和响应性,减少内存占用。 #### 6. Class.forName与Class.getClassLoader - **Class.forName**: - 加载指定类,并返回...

    互联网创意产品众筹平台

    │ 7-登录功能异步开发总结 │ 8-MD5概述5 _* g* f: Y1 v* o4 H │ 9-MD5-工具类1 H6 x* t" K- z* B │ 10-登录功能-MD5密码加密' F4 `+ B( c' b5 I' ?7 a │ 11-注销功能9 z3 d8 y4 A0 l: p* n* @ │ 12-附录3.页面...

    java源码总结-Java-Concurrent-Programming:java并发编程学习总结源码部分

    本项目“Java-Concurrent-Programming”是一个基于《Java并发编程艺术》这本书的学习总结,通过源码分析来深入理解并发编程的核心概念。 在Java中,线程是并发的基础,`Thread`类提供了创建和管理线程的基本功能。...

    Hessian源码分析和Hack.doc

    总结,Hessian作为高效的RPC框架,虽然没有内置获取客户端信息的功能,但通过分析其源码并适当扩展,我们能够实现这一需求。需要注意的是,直接修改开源库的源码可能带来维护上的困扰,因此在可行的情况下,推荐使用...

    Java互联网架构多线程并发编程原理及实战 视频教程 下载4.zip

    Java互联网架构多线程并发编程原理及实战 ...11-1 数据同步接口--需求分析.mp4 11-2 中间表设计.mp4 11-3 基础环境搭建.mp4 11-4 生产者代码实现.mp4 11-5 消费者编码实现.mp4 12-1 课程总结.mp4 笔记课件.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载2.zip

    Java互联网架构多线程并发编程原理及实战 ...11-1 数据同步接口--需求分析.mp4 11-2 中间表设计.mp4 11-3 基础环境搭建.mp4 11-4 生产者代码实现.mp4 11-5 消费者编码实现.mp4 12-1 课程总结.mp4 笔记课件.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载3.zip

    Java互联网架构多线程并发编程原理及实战 ...11-1 数据同步接口--需求分析.mp4 11-2 中间表设计.mp4 11-3 基础环境搭建.mp4 11-4 生产者代码实现.mp4 11-5 消费者编码实现.mp4 12-1 课程总结.mp4 笔记课件.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载.zip

    Java互联网架构多线程并发编程原理及实战 ...11-1 数据同步接口--需求分析.mp4 11-2 中间表设计.mp4 11-3 基础环境搭建.mp4 11-4 生产者代码实现.mp4 11-5 消费者编码实现.mp4 12-1 课程总结.mp4 笔记课件.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载1.zip

    Java互联网架构多线程并发编程原理及实战 ...11-1 数据同步接口--需求分析.mp4 11-2 中间表设计.mp4 11-3 基础环境搭建.mp4 11-4 生产者代码实现.mp4 11-5 消费者编码实现.mp4 12-1 课程总结.mp4 笔记课件.zip

    ikm_java_8.pdf

    通过以上分析,我们了解了Java 8中关于多线程安全的一些技巧、JavaScript表单验证的基本原理、如何在Servlet中读取初始化参数以及Swing组件类型的区别。这些知识点对于开发高质量的Java应用程序至关重要。

    笔试面试题国内知名公司

    - 接口与抽象类的使用场景分析 - 继承与多态的深入探讨 4. **Java并发编程** - 线程的创建与同步,如synchronized、volatile关键字 - Lock接口和ReentrantLock的使用 - ThreadLocal原理及应用场景 - 并发工具...

Global site tag (gtag.js) - Google Analytics