书上有一个工具类 HibernateUtil,书本解释它的作用是:
“将Hibernate Session 存放在一个ThreadLocal变量中,对于同一个线程的请求,将可以轻松访问该Session”
“可以保证将线程不安全的Session绑定限制在当前线程内——也就是实现‘上下文相关的’Session”我不明白的是—— 既然从 SessionFactory.openSession() 获取 Session 不是单例模式,也就是每次获取 Session都是不同的,那么为什么会出现 多个线程同时访问同一个Session的问题呢?比如,在一个Web应用中,在处理用户请求的Servlet中通过 SessionFactory.openSession() 获取一个Session,然后访问数据库,最后关闭Session,按理说每次用户请求所用到的 Session 应该是不同的,应该不会出现多个线程同时访问同一个Session造成的线程安全问题啊,为什么要用工具类 HibernateUtil呢??不解~~恳求各位高手慷慨解惑,万分感激!!!

解决方案 »

  1.   

    嗯 楼主是个很是在的家伙,属于圣斗士那类型的,不错,就凭这股干劲儿,相信你在今后会大牛!!
    ====================================================================================
    谢谢fpy_061625的夸奖,对于编程其实我只是个菜鸟,很惭愧!我只是觉得:搞编程,学任何一门技术、工具(比如Hibernate),理解其实现原理、思想是最重要的,反而语法、配置、操作是其次——当你理解了其实现原理、思想,就不会被它那些纷繁复杂的配置、操作所迷惑。理解了就不会觉得它很神秘:“哦,这种设计如果让我来搞,也许我也会这样想”,还有可能发现它的不足从而改进它,甚至有必要时自己开发一套更加先进的工具;反之如果不理解,即使对语法、配置、操作烂熟于心,它都不是你的,人家工具怎么改怎么变,你只能跟着走,自己没有任何主动权。我本身是读数学的,也许还带着这样一种习惯 —— 面对一大堆纷繁复杂的抽象符号、式子、数字、曲线,如果你头脑中对其背后的思路、原理有个很清晰的概念,就永远不会被它疑惑,否则见到就头疼。第一次发帖提问,希望高手们可以多多指教,感激不尽!当人也希望结识有相同兴趣、志向的朋友。
      

  2.   

    提问中提到的Web应用的例子,为了简单起见,我省掉了Dao层等东西,访问数据库直接在控制器Servlet里面进行,实际项目应该很少这个样子。
      

  3.   

    http://caoyinghui.javaeye.com/blog/523902这个地址上写着不错,你可以看看
      

  4.   


    对于SessionFactory.openSession() 来说,同一个线程内不管做多少次
    session1=SessionFactory.openSession() ;
    session2=SessionFactory.openSession() ;
    session1=session2;
    创建的session都是相等的,这样保证了session内操作数据的完整性和隔离性。
    反之则不相当
      

  5.   


    对于SessionFactory.openSession() 来说,同一个线程内不管做多少次 
    session1=SessionFactory.openSession() ; 
    session2=SessionFactory.openSession() ; 
    session1=session2; 
    创建的session都是相等的,这样保证了session内操作数据的完整性和隔离性。 
    反之则不相当
    ===================================================================================
    谢谢回复!
    但是我尝试过,在这种情况下执行   System.out.println(session1==session2),
    结果等于false, 说明两次获取的session是不同的。而且我的意思是:想看看有什么情况会出现多个线程同时共享一个session问题,因为我想搞明白那个工具类HibernateUtil的存在究竟有什么意义,目前我想不到任何一定要用到它的例子。
      

  6.   

    ================================================================================
    谢谢推荐!但我粗略看了一下,那帖子是介绍怎样实现保证“一个线程上最多只能创建一个session实例 .并且每个线程都能创建一个实例”的问题,没有提到怎样会出现多个线程同时访问一个session的问题。
      

  7.   

    个人觉得SessionFactory.openSession()每次获取的session是不同的,但在servlet中存在线程安全问题,如果把session声明为servlet的实例变量,就会出现多个线程访问一个session 的情况。
      

  8.   


    首先非常感谢fudoublelong朋友的回复!“把session声明为servlet的实例变量,就会出现多个线程访问一个session 的情况”——
    这个很容易理解,这样相当于多个线程同时访问同一个Servlet实例,那么当然也共享它的成员变量(属性)session,毫无疑问。但问题是为什么要这样做?为什么不把session声明为Servlet方法的局部变量(此时相当于要用session实例时就创建、用完就关闭、销毁)? 这样做不是很方便且节省内存吗??会有什么负面作用??(好像创建session实例代价开销不大)盼指教!
      

  9.   

    没用过hibernate,胡说一下hibernate使用ThreadLocal,
    主要是为了“‘上下文相关的’Session
    也就是说,使用Threadlocal对当前进程进行判断,
    如果是有[这个线程的session]就直接返回。而lz担心的“多个线程”,可能就有了多了session了。随便胡说一下,
    等待被拍砖bdgood luck
      

  10.   


    谢谢回复!大家交流,不存在什么“胡说”,只要多交流即使没有最终的结果,都会有收回的。
    但可能你误解了我的疑问,我不是对ThreadLocal的作用有什么怀疑。
      

  11.   

    比如,在一个Web应用中,在处理用户请求的Servlet中通过 SessionFactory.openSession() 获取一个Session,然后访问数据库,最后关闭Session,按理说每次用户请求所用到的 Session 应该是不同的,应该不会出现多个线程同时访问同一个Session造成的线程安全问题啊,为什么要用工具类 HibernateUtil呢??不解~~ 如果你的session在你的Servlet是实例变量,那你的多个线程访问的session对象肯定是同一个对象,
    这样就会出错了。
      

  12.   


    谢谢您的回复!
    上面12楼的朋友也提到您所说的这个问题,但我还是有疑问——为什么要将session设置为Servlet的实例变量?为什么不把session声明为Servlet方法的局部变量(此时相当于要用session实例时就创建、用完就关闭、销毁)? 这样做既可避免发生多个线程同时访问一个Session带来的线程安全问题,也很方便,又节省内存,会有什么负面作用吗??(好像创建session实例的代价、开销不大) 
      

  13.   

    ThreadLocal保证不同的线程有不同的Session的.
      

  14.   


    谢谢回复!
    但我的疑问不是ThreadLocal作用的问题。
    我的疑问是为什么会出现多个线程同时访问同一个Session的情况,如果可以简单地避免这种情况(比如像我所说的把Session声明为局部变量,要用就建,用完立刻销毁,根本不会出现多线程访问冲突),又没什么负面作用的,那么要这个ThreadLocal有什么用??
      

  15.   

    System.out.println(session1==session2)
    当然是false的,这个是基本概念里的了,两个对象的相等判断,你这样肯定是false
    先不说他们是否是一个session,就算是,那也只能说是两个不同的引用,指向同一个session对象了,本身session1 和 session2是两个不同的对象
      

  16.   

    想了很久也想不出来多个线程同时访问同一个Session的情况。等待牛人解答。
    对于ThreadLocal的作用,我觉得只是为了避免一个线程中过于频繁的openSession和closeSession,在Web应用中,可以在filter中统一管理。
      

  17.   

    多个线程访问同一个Session就是一个常见的事情,你用一个frameset,里面包含3个frame,那么IE就同时向服务器发出三个请求,这三个请求都是同一个session的了!而每个请求就是一个线程处理的,所以多个线程访问同一个Session是非常常见的事情。
      

  18.   

    ThreadLocal的用处在于不需要将Hibernate的Session逐层传递。
    而如果你每次在访问时都打开新的Hibernate的Session,那么事务等都不方便进行处理。所以很多时采用的是每一次Http请求只打开一个Hibernate的Session及事务。使用ThreadLocal就可以做到这一点
      

  19.   

    如果作为局部变量,照你说的这个流程当然可以。但是在高并发的情况下,会影响性能。
    (好像创建session实例的代价、开销不大)//创建session的开销在高并发情况下是很大的。而用thread可以解决这个问题:
    “将Hibernate Session 存放在一个ThreadLocal变量中,对于同一个线程的请求,将可以轻松访问该Session
    当同一个线程再次请求时,只需从threadlocal中获取 而无需再创建。
      

  20.   

    很欣赏楼主的学术精神  :)不知道我是不是正确理解了楼主的疑问。我想你的疑问是:既然 SessionFactory.openSession() 每次获得的都是不同的对象,那就根本不存在多线程访问冲突的问题,为什么还要搞 ThreadLocal 之类的东西来解决这个所谓的问题?如果是这个问题,那我是这样理解的:“多线程访问冲突”的问题,并不是一开始需要解决的问题,而是为了解决另外一个问题时产生的问题。真正要解决的是“如何在程序处理过程中随时能得到一个 session 对象,用于数据库操作”。一个 request 被 servlet 处理的过程中,会穿透很多层代码,用参数传递显然不方便,所以要能够“随时得到”,但同时又希望整个过程中得到的都是同一个 session 对象,这样才方便事务控制等要求。那么怎么办呢?“单例”和“全局变量”都可以达到这个目标,但都会产生“多线程访问冲突”的问题。于是就有了 ThreadLocal 这种类似于“全局变量”的方法。
      

  21.   


    我感觉您是理解了lz的问题了,
    可是您回答的还是拐到了“为什么使用ThreadLocal”上。
    也不是lz的核心问题-“会不会有情况遇到多线程冲突”和“什么情况下会遇到”占地学习bdgood luck
      

  22.   

    那我就再补充两句  :p为了解决“如何在程序处理过程中随时能得到同一个 session 对象,用于数据库操作”的问题,如果我们只是简单地采用了“单例”或者“全局变量”的方法,那立刻就会出现“多线程访问冲突”的问题了。还是那句话,这个“多线程访问冲突”的问题并不是原本就存在的问题,而是为了解决另一个问题而可能产生出来的问题。
      

  23.   

    也就是捎带手儿的把“多线程访问冲突”也解决了?也就是“搂草打兔子”喽?一股寒意...good luck
      

  24.   

    可能是我们的语言表达习惯有差异。我前面说的内容中绝对没有捎带手儿的把“多线程访问冲突”也解决了的意思,恰恰相反,原本根本就不存在“多线程访问冲突”的问题。另外,你的寒意从何而来?不会是属兔子的吧?hehe,just kidding :)
      

  25.   

    应该说Session使用了单例模式的,所以其实还是一个session对象
      

  26.   

    其实产生这个问题的根源在于,我们不希望经常性的打开、关闭Hibernate的Session对象,而是每一次Http请求只产生一个Session所以需要将这个openSession返回的Session对象保存在某个变量中,以方便下一个方法继续使用但是要放哪里呢?如果放在一个类的静态变量中,那么另外一个请求也会意外的得到了这个Hibernate的Session进行操作了,所以这是不可行的。而放在一个静态变量ThreadLocal中,则可以另外一个请求(肯定与当前请求所在线程不同)同样访问ThreadLocal的时候,ThreadLocal可以保证当前请求与另外一个请求是不会发生冲突和混淆的,而是相互独立的(即当前请求set(sessionA)以后,另外那个请求get是取不出sessionA的)所以通常会用ThreadLocal来作为存储Hibernate的Session对象
      

  27.   


    估计lz就是要看这句“原本根本就不存在“多线程访问冲突”的问题。” 我寒是因为...现在是冬天。
    ...哈哈good luck
      

  28.   


    感谢回复!
    您的回答对我很有启发意义,我有点理解ThreadLocal在此处的意义了,不错!!
      

  29.   


    感谢回复!
    您的回答对我也很有用!我明白了Threadlocal的意义了,不会再纠缠于“会否有多线程并发一个session的问题”。
      

  30.   


    感谢回复!
    我对您的回答感到非常满意!!
    您已经很理解我的疑惑,got the point! 并且回答得很详细、很到位,我明白这个ThreadLocal 的作用了,再次感谢!还有几个相关的问题想请教,
    (1)session.beginTransaction()、session.commit()是不是通知数据库执行一次相应的“开启事务”和“提交事务”的指令??因为据我所知道,应用程序事务和数据库事务并不一定是一 一对应的
    (2)如果(1)的答案是肯定的话,那么session.beginTransaction()、session.commit()之间的代码(执行时间)应该尽可能地短,否则会阻止数据库的并发修改从而影响网站性能,对吗?
    (3)在一个线程中,每次要用到 ThreadLocal中的Session的时候,都应该是:开启事务--> 持久化操作-->提交事务,而无须每次都关闭Session,Session留待最后一次用完后才关闭,对吗??
    若能回答,感激不尽!!
      

  31.   

    看到楼主对每个回帖都认真总结、回应,我很感动 ^_^你说的这三个问题,我没有十足的把握,尽我所知说一下吧,抛砖引玉。(1) 回答这个问题最彻底的办法是阅读 Hibernate 的源代码。我现在没去读源代码,凭感觉,这个可能跟具体的实现有关,最简单的实现当然就是直接执行数据库对应的操作而已。但就像你说的,应用程序事务和数据库事务并不一定是一一对应的,举个例子来说,如果启用了“外部事务服务”的话,在一个“事务”中可能就不仅仅是一个数据库的操作,而可能有多个数据库的联动操作,甚至可能有文件系统操作,等等。(2) 这是个一般性的要求,永远是正确的 :) 不过也不是绝对的,如果真有复杂的更新操作,也可以想别的办法来使得写操作不会长时间阻塞读操作,比如通过 replication 的方式来部署多个数据库实例,一写多读。(3) 差不多是这个意思。不过更准确地说,连“开启事务”和“提交事务”也不是每次一定要做的,根据需要进行配置,有可能就要做成“最开始开启一次,最后提交一次”形式的。
      

  32.   

    sorry, 刚刚注意到楼主的问题中说的是“并发修改”的问题,我回答的是“读写冲突”的问题,文不对题,忘记它吧。并发修改确实可能成为问题,如果多个修改操作是冲突的,肯定得一个等另一个了,要是不直接冲突的话,也许问题不大。不管怎么说,“尽可能缩短事务中操作所耗费的时间”总是没错的 :)
      

  33.   

    楼主把那个HibernateUtil的代码帖出来嘛,看看他究竟为什么那样说
      

  34.   

    我认为session不能共享,我觉得一个请求就是建立一个session连接,如果共享了,线程1把session关闭了,那其他几个线程就不能对session操作了。
    我今天也是对多线程,单实例疑惑,上面这个是我个人看法,望各位给指导指导。
      

  35.   

    因为你可以写代码实现多个线程访问同一个SESSION
      

  36.   

    将Hibernate Session 存放在一个ThreadLocal变量中
    那就是说明了每对对象访问的时候为其出创建了一个session的副本,所以会不相等
    但是都是同一个对象的副本
      

  37.   

    我的理解:把session放在ThreadLocal中的目的是:
    不用显示地在本次请求中传递session,简化代码。而不是为了控制多线程问题。sf好像还有个方法叫xxxxCurrentSession(),可以返回不同的session。
      

  38.   

    有的时候你自己需要定一个Service层的对象或者是Servlet,这个Servlet或者Action中需要用到Session,因此,你把Session定义为了这个Action的成员变量,而Action或Servlet是完全可能被多个线程同时访问的,所以你的Session变量就有可能会被多线程同时操作了,呵呵。
      

  39.   

    sessionFactory.openSession()  可以书中讲解有问题,如果每次都openSession()的话是非常耗资源的,相当于jdbc中的connection,为了节省资源,可以把每个线程的session归类(意思就是一个线程使用一个session),ThreadLocal其它内部实现就是像map一样,key是当前Thread,value在该例中就是session,那使用get的时候内部实现就是get("key")  ,key可以通过Thread.currentThread();方法获取,这样就可以获取到当前线程中绑定的session,直接拿过来就可以用了.
    ThreadLocal的使用就是在单例的情况下如何保存各线程中保存的变量值
    至于你说的openSession()每次获取到的对象都是不一样的,你的理解是对的,的确不一样的
      

  40.   

    这个问题可以通过两个角度来解释:
    1、HibernateUtil是帮助你获得与线程上下文相关的Session,Session的这个特性我个人概括为线程敏感性,即每个Session一般都是和一个具体的线程相关的,是线程敏感的,这是Session的一个重要特性,为什么要强调线程敏感呢?或者说程序中什么为什么需要和当前线程上下文相关的那个Session呢?是因为Session是承载事物的一个载体,一般都是通过Session来创建一个Transaction,而Transcation创建的位置有的时候需要跨DAO层,例如银行转账业务,有一个DAO对象叫Deposit.java表示存款操作,还有一个DAO对象叫Withdraw.java,即取款对象,由于转账需要同时调用两个操作,因此事物应该在业务逻辑层开启,于是,在每个DAO对象的代码中就存在如何获得在业务层已经获取到的那个事物的代码,这个时候就需要Session.getcurrent方法,于是你就得到了和这个线程相关的Session,而这个session是在业务逻辑层开启的,它可以被下面涵盖的多个DAO操作通过ThreadLocal方式来共享,HibernateUtil在Hibernate3中已经不用了,因为早期的Hibernate是不提供getcurrent方法来获得线程敏感的Session的,后来Hibernate3了,这个功能就加上了。呵呵。上次回答的时候没有注意,没看清楚问题,今天仔细又看了一下楼主的问题,希望你可以明了。你现在的问题是不要将HibernateUtil类的功能与所谓的“多个线程为什么要访问同一个Session”这样的问题联系起来呢,这两个东西本来就没有关系,人家建立HibernateUtil也不是为了这个,呵呵。2、Session除了线程敏感特性以外,还包括线程非安全性。如何理解Session的线程非安全性?其实很简单,由于每个线程都有自己的上下文的Session,而Session类自身的方法是不提供资源访问方面的同步性的,因此Session不是线程安全的,因此在有些时候,通过Session来访问数据库的时候要考虑并发的问题,而并发可以通过什么机制来控制呢?当然是锁!乐观锁、悲观锁等等而锁控制的你读完之后谁能写,但有这个还不够,有的时候你还需要控制“你写完之后谁能读”,这是怎么实现的呢?当然是事务隔离级别,即所谓的提交读,为提交读,重复读等等。以上内容不知道对楼主整理思路是否有帮助,呵呵。
      

  41.   

    首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。 
      

  42.   

    我觉得问题应该要从0开始考虑:假设让你来设计这个Session你会怎么想呢?
    设计的初衷:提高性能
    设计陈述:
    1)尽量只访问内存不访问硬盘(不访问硬盘是不可能的,这里指减少次数)
    2)Session一个轻量级对象,它对性能的贡献并不是很大,所以它的另外一个作用应该就是要保证一个事务的正常完成(当事务2发现Session的缓存a被事务1占用中,它应该要new一个Session的缓存b来供给事务2;相反,当事务2发现Session的缓存a空闲,那就应该直接占用它。这是由事务的原子性决定的,所以你设计的Session也应该尽量遵循它,把它的错误限定在事务本身内部)