`
shishi11
  • 浏览: 116841 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

threadlocal的介绍(转)

阅读更多
ThreadLocal是什么

    ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单, 就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每 一个线程都完全拥有该变量。线程局部变量并不是Java的新发明,在其它的一些语言编译器实现(如IBM XL FORTRAN)中,它在语言的层次提供了直接的支持。因为Java中没有提供在语言层次的直接支持,而是提供了一个ThreadLocal的类来提供支 持,所以,在Java中编写线程局部变量的代码相对比较笨拙,这也许是线程局部变量没有在Java中得到很好的普及的一个原因吧。

   ThreadLocal的设计

   首先看看ThreadLocal的接口:

    Object get() ; // 返回当前线程的线程局部变量副本 protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值
    void set(Object value); // 设置当前线程的线程局部变量副本的值

    ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实 现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行, 并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:

protected Object initialValue() { return null; }

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

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;
  }
}

  当然,这并不是一个工业强度的实现,但JDK中的ThreadLocal的实现总体思路也类似于此。

   ThreadLocal的使用

   如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类化,比如下面的例子,SerialNum类为每一个类分配一个序号:

public class SerialNum
{
  // The next serial number to be assigned

  private static int nextSerialNum = 0;
  private static ThreadLocal serialNum = new ThreadLocal()
  {
   protected synchronized Object initialValue()
   {
    return new Integer(nextSerialNum++);
   }
  };

  public static int get()
  {
   return ((Integer) (serialNum.get())).intValue();
  }
}

  SerialNum类的使用将非常地简单,因为get()方法是static的,所以在需要获取当前线程的序号时,简单地调用:

int serial = SerialNum.get();

  即可。

   在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失效,并等待垃圾收集器收集。

   ThreadLocal与其它同步机制的比较

    ThreadLocal和其它同步机制相比有什么优势呢?ThreadLocal和其它所有的同步机制都是为了解决多线程中的对同一变量的访问冲突,在普 通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需要很细致地分析在什么时候对变量 进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一个角 度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的 变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进 ThreadLocal,或者把该对象的特定于线程的状态封装进ThreadLocal。

   由于ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal get当前线程的值是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数的ThreadLocal< T>类将从中受益。也可以减少强制类型转换,并将一些错误检查提前到了编译期,将一定程度地简化ThreadLocal的使用。

   总结

    当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信 的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所 以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程 序,使程序更加易读、简洁。

最近对Hibernate的ThreadLocal Session模式有点兴趣。于是根据曹晓钢翻译的Hibernate Reference做了个小测验,结果发现了一个小bug。
代码很简单,都是利用Hibernate Reference中现成的代码。
首先是一个辅助的得到线程安全的session的HibernateUtil类,

public class HibernateUtil {
public static final SessionFactory sessionFactory;
static{
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
}
catch(Throwable ex){
throw new ExceptionInInitializerError(ex);
}
}

public static final ThreadLocal session = new ThreadLocal();

public static Session currentSession()
{
Session s = (Session) session.get();
if (s==null )
{
s = sessionFactory.getCurrentSession();
session.set(s);
}
return s;
}

public static void closeSession()
{
Session s = (Session) session.get();
if (s!=null)
s.close();
session.set(null);
}

public static SessionFactory getSessionFactory()
{
return sessionFactory;
}
}

然后是一个测试插入数据的代码。也很简单,也是仿Hibernate Reference上面的代码。

public class InsertUser {
public static void main(String[] args) {
Sessionsession = HibernateUtil.currentSession();
Transaction tx= session.beginTransaction();
TUser user = new TUser();
user.setName("Emma");
session.save(user);
tx.commit();
HibernateUtil.closeSession();
}
}

就这么简单一个程序,运行到最后,出现一个错误。

org.hibernate.SessionException: Session was already closed
at org.hibernate.impl.SessionImpl.close(SessionImpl.java:270)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:301)
at $Proxy0.close(Unknown Source)
at Util.HibernateUtil.closeSession(HibernateUtil.java:36)
at test.InsertUser.main(InsertUser.java:20)
Exception in thread "main"

错误出现在 HibernateUtil.closeSession(); 这一行,意思是session已经关闭了,再次关闭它就引起异常了。

不过前面的代码中只有个tx.commit(); 提交事务 而已,并没有自动关闭session啊?

于是把DEBUG信息调用出来,发现了以下几句提示:
DEBUG [main] - after transaction completion
DEBUG [main] - automatically closing session
DEBUG [main] - closing session
DEBUG [main] - connection already null in cleanup : no action
DEBUG [main] - allowing proxied method [close] to proceed to real session
DEBUG [main] - closing session
org.hibernate.SessionException: Session was already closed


特别是下面这3句话引起了我的注意,果然是session关闭了,而且是在 事务结束以后自动关闭的。
DEBUG [main] - after transaction completion
DEBUG [main] - automatically closing session
DEBUG [main] - closing session

那么这个机制是怎么发生的呢?

打开了Hibernate3的源码,我找到了答案。

首先,根据sessionFactory = new Configuration().configure().buildSessionFactory();
打开Configuration类的buildSessionFactory()方法,找到sessionFactory的生成语句
return new SessionFactoryImpl(
this,
mapping,
settings,
getInitializedEventListeners()
);
,然后找到SessionFactoryImpl的getCurrentSession方法,发现是这么定义的。

public org.hibernate.classic.Session getCurrentSession() throws HibernateException {
if ( currentSessionContext == null ) {
throw new HibernateException( "No CurrentSessionContext configured!" );
}
return currentSessionContext.currentSession();
}

他调用的是一个currentSessionContext的currentSession方法。查找currentSessionContext变量,

currentSessionContext = buildCurrentSessionContext();

,知道了buildCurrentSessionContext方法产生了这个currentSessionContext 对象。

private CurrentSessionContext buildCurrentSessionContext() {
String impl = properties.getProperty( Environment.CURRENT_SESSION_CONTEXT_CLASS );
// for backward-compatability
if ( impl == null && transactionManager != null ) {
impl = "jta";
}

if ( impl == null ) {
return null;
}
else if ( "jta".equals( impl ) ) {
return new JTASessionContext( this );
}
else if ( "thread".equals( impl ) ) {
return new ThreadLocalSessionContext( this );
}
else {
try {
Class implClass = ReflectHelper.classForName( impl );
return ( CurrentSessionContext ) implClass
.getConstructor( new Class[] { SessionFactoryImplementor.class } )
.newInstance( new Object[] { this } );
}
catch( Throwable t ) {
log.error( "Unable to construct current session context [" + impl + "]", t );
return null;
}
}
}

这个方法就是用来判断使用JTA管理这个SessionContext还是用ThreadLocal来管理SessionContext的。
在我们这里是用 ThreadLocal 来管理的,于是找到了currentSessionContext 的实现类是 ThreadLocalSessionContext。

找到该类的currentSession方法

public final Session currentSession() throws HibernateException {
Session current = existingSession( factory );
if (current == null) {
current = buildOrObtainSession();
// register a cleanup synch
current.getTransaction().registerSynchronization( buildCleanupSynch() );
// wrap the session in the transaction-protection proxy
if ( needsWrapping( current ) ) {
current = wrap( current );
}
// then bind it
doBind( current, factory );
}
return current;
}

然后跟踪到 buildOrObtainSession(),就是这里,打开了session。

protected Session buildOrObtainSession() {
return factory.openSession(
null,
isAutoFlushEnabled(),
isAutoCloseEnabled(),
getConnectionReleaseMode()
);
}

注意第三个参数:isAutoCloseEnabled
打开Session这个接口,看到 openSession方法中这个参数是如下描述的:
* @param autoCloseSessionEnabled Should the session be auto-closed after
* transaction completion?

,就是说session是否应该在事务提交后自动关闭。

然后打开 ThreadLocalSessionContext 的isAutoCloseEnabled()方法。

/**
* Mainly for subclass usage. This impl always returns true.
*
* @return Whether or not the the session should be closed by transaction completion.
*/
protected boolean isAutoCloseEnabled() {
return true;
}

看到如下提示:Whether or not the the session should be closed by transaction completion ,即无论如何session应该在事务完成后关闭。

答案就在这里,就是说在ThreadLocal Session模式下面,只要提交了事务,那么session就自动关闭了,因此我参照Hibernate Refernece上面的代码写的在事务关闭以后再调用HibernateUtil.closeSession();是不对的,这句代码是完全多余的。


分享到:
评论

相关推荐

    ThreadLocal:如何优雅的解决SimpleDateFormat多线程安全问题

    目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date日期转String类型(format)SimpleDateFormat出现bug的原因如何解决SimpleDateFormat多线程安全问题局部变量使用...

    字符串转日期

    ### 字符串转日期知识点详解 #### 一、概述 在日常开发工作中,我们经常会遇到需要将日期格式的字符串转换为`Date`类型的情况。这种转换对于数据处理、时间比较等操作至关重要。本文将详细介绍如何在Java环境中...

    Java多线程环境下SimpleDateFormat类安全转换

    本文主要介绍了Java多线程环境下SimpleDateFormat类的安全转换,通过示例代码详细介绍了如何解决SimpleDateFormat类多线程环境下转换错误问题。 1. SimpleDateFormat类的线程安全问题 SimpleDateFormat类是Java中...

    Java时间格式转化

    1. **线程安全问题**:`SimpleDateFormat`不是线程安全的,在多线程环境中应该避免共享实例,或者使用`ThreadLocal`来管理。 2. **异常处理**:在进行字符串到`Date`的转换时,一定要注意捕获并处理`ParseException`...

    Java很好的学习笔记4 无锁.md,学习代码

    - "TimeUnit.md"可能详细介绍了Java中的时间单位转换,这对于理解和控制线程的等待、睡眠等操作非常重要。 - "并发编程.pdf"和"并发编程_应用.pdf"可能是全面的并发编程教程,涵盖了Java内存模型、线程池、信号量、...

    java.lang包介绍

    开发者应避免在新代码中使用这些过时的方法,转而采用推荐的替代方案。 总之,`java.lang`包是Java编程的核心,提供了基础的类型、对象操作、异常处理、线程支持等关键功能,是理解和使用Java的基础。

    视频_c#3.0,VB9.0 新特性介绍

    1. **隐式线程局部变量**:VB 9引入了`ThreadLocal(Of T)`,使得开发者可以创建在每个线程上拥有独立值的变量。 2. **隐式类型(Option Infer)**:允许VB编译器根据初始值推断变量的类型,使代码更简洁。 3. **...

    Python web全栈学习路线.docx

    * ThreadLocal * GIL 全局解释器锁 * 协程介绍 * gevent 库和 greenlet 库 网络编程 * socket 套接字 * tcp 协议 * tcp 三次握手和四次挥手 * udp 协议 * 端口 * tcp 发送和接收数据 * udp 发送和接收数据 * 仿 QQ...

    C#中的生成随机数代码

    本文将详细介绍如何在C#中生成随机数,以及几种不同的实现方式。 首先,让我们了解C#中最基础的随机数生成方法。C#提供了一个名为`System.Random`的类,可以用来生成一系列伪随机数。以下是最简单的使用示例: ```...

    Java并发编程实践PDF电子书

    线程局部变量(ThreadLocal)在本章中被详细阐述,它为每个线程提供独立的变量副本,确保线程之间的数据隔离,常用于解决并发场景下的数据共享问题。 第八章:并发异常处理 章节重点讨论了在并发环境中如何正确地...

    java技术指南

    在JDK8源码分析章节中,文档首先介绍了Unsafe类,它提供了硬件级别的操作支持,然后转向了java.lang包下的各个核心类,如String、Thread、ThreadLocal等。对于java.util包下的集合框架,文档不仅介绍了各种集合类,...

    《java并发编程艺术》

    - **线程不安全**:书中会介绍线程不安全的常见例子,如共享数据的未同步访问可能导致的数据不一致问题。 - **同步机制**:包括`synchronized`关键字、`wait()`/`notify()`和`notifyAll()`方法,以及`Lock`接口和...

    廖雪峰python3教程

    11. 进程和线程:对多进程、多线程以及它们之间的区别和联系进行了讨论,并介绍了线程本地存储(ThreadLocal)和分布式进程的概念。 12. 正则表达式:简要介绍了正则表达式的基本用法和常用内建模块。 13. 网络...

    Thinking.In.Java.Third.Edition.CHS.zip

    书中讲解了线程的创建、同步、互斥以及并发编程的各种策略,如synchronized关键字、wait()和notify()方法,以及ThreadLocal和ExecutorService等高级话题。 I/O流系统是Java处理输入输出的关键部分。书中介绍了不同...

    JAVA线程第三版

    7. **线程局部变量**:ThreadLocal提供线程私有的变量副本,避免了共享数据带来的同步问题。 8. **并发设计模式**:书中还探讨了各种并发设计模式,如生产者消费者模型、读写锁等,这些都是解决多线程问题的常用...

    廖雪峰Python3.5教程(不带标签)

    - **ThreadLocal**:解决多线程环境中变量共享的问题。 - **进程与线程的区别**:比较进程和线程的特点及适用场景。 #### 分布式进程 - **概念**:解释分布式进程的基本原理。 - **实现方式**:使用如...

    日期处理工具

    这篇名为“日期处理工具”的博文可能详细介绍了如何使用Java的内置类或者第三方库(如Apache Commons Lang的DateUtils,Java 8的java.time包)来处理日期。 描述中的“NULL”意味着没有提供额外的信息,所以我们...

    良葛格java jdk 5.0学习笔记

    《良葛格Java JDK 5.0学习笔记》是一份专为Java初学者及爱好者精心编写的教程,它深入浅出地介绍了Java编程语言的核心概念和技术。这份教材以JDK 5.0版本为基础,该版本是Java发展史上的一个重要里程碑,引入了许多...

Global site tag (gtag.js) - Google Analytics