阅读257 返回首页    go 京东网上商城


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方法,以给定一个特定的初始值。


public class ThreadLocal
{
 private Map values = Collections.synchronizedMap(new HashMap());
 public Object get()
 {
  Thread curThread = Thread.currentThread(); 
  Object o = values.get(curThread); 
  if (o == null && !values.containsKey(curThread))
  {
   o = initialValue();
   values.put(curThread, o); 
  }
  return o; 
 }


 public void set(Object newValue)
 {
  values.put(Thread.currentThread(), newValue);
 }


 public Object initialValue()
 {
  return null; 
 }




三 典型实例
 
Hiberante的Session工具类HibernateUtil。这个类是Hibernate官方文档中HibernateUtil类,用于session管理。
 
public class HibernateUtil 
{
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final 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
    public static Session currentSession() throws HibernateException 
    {
        Session s = (Session) session.get();
        if (s == null) 
        {
            // 将新开的Session保存到线程局部变量中
            s = sessionFactory.openSession();
            session.set(s);
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException 
    {
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}




对代码进行分析


1  public static final ThreadLocal session = new ThreadLocal(); 
2  public static Session currentSession() { 
3      Session s = (Session)session.get(); 
4      if(s == null){ 
5          s = sessionFactory.openSession(); 
6          session.set(s); 
7      } 
8      return s; 
9  } 


总体分析

该类由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null。第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此对session做了判断如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键。


逐行分析

第一行 
初始化。如果不初始化initialvalue则initialvalue为null。 

第三行
session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session相当于对应每个数据库连接。多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s数据库连接。 

第四行
如果是该线程初次访问,自然s会是null,接着创建一个Session,具体就是第五行。 

第五行
创建一个数据库连接实例s。

第六行
保存该数据库连接s到ThreadLocal中。 

第七行
如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。 



四 总结
 

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本。

 

原帖地址:https://lavasoft.blog.51cto.com/62575/51926/


 


最后更新:2017-04-04 07:32:04

  上一篇:go 开发人员对测试人员最常说的20句话
  下一篇:go Oracle中序列的操作以及使用前对序列的初始化