去看了SessionFactoryImpl的源码。里面的实例变量大部分是final类型的,不可改变。但是有两个transient类型非final的。      再看SessionImpl的源码。大部分的实例变量是transient非final类型的。但是有一个      private EntityNameResolver  entityNameResolver = new CoordinatingEntityNameResolver();      这如何解释实例变量影响线程安全呢?public final class SessionFactoryImpl implements SessionFactory, SessionFactoryImplementor { private static final Logger log = LoggerFactory.getLogger(SessionFactoryImpl.class);
private static final IdentifierGenerator UUID_GENERATOR = new UUIDHexGenerator(); private final String name;
private final String uuid; private final transient Map entityPersisters;
private final transient Map classMetadata;
private final transient Map collectionPersisters;
private final transient Map collectionMetadata;
private final transient Map collectionRolesByEntityParticipant;
private final transient Map identifierGenerators;
private final transient Map namedQueries;
private final transient Map namedSqlQueries;
private final transient Map sqlResultSetMappings;
private final transient Map filters;
private final transient Map imports;
private final transient Interceptor interceptor;
private final transient Settings settings;
private final transient Properties properties;
private transient SchemaExport schemaExport;
private final transient TransactionManager transactionManager;
private final transient QueryCache queryCache;
private final transient UpdateTimestampsCache updateTimestampsCache;
private final transient Map queryCaches;
private final transient Map allCacheRegions = new HashMap();
private final transient StatisticsImpl statistics = new StatisticsImpl(this);
private final transient EventListeners eventListeners;
private final transient CurrentSessionContext currentSessionContext;
private final transient EntityNotFoundDelegate entityNotFoundDelegate;
private final transient SQLFunctionRegistry sqlFunctionRegistry;
private final transient SessionFactoryObserver observer;
private final transient HashMap entityNameResolvers = new HashMap(); private final QueryPlanCache queryPlanCache = new QueryPlanCache( this ); private transient boolean isClosed = false;public final class SessionImpl extends AbstractSessionImpl
implements EventSource, org.hibernate.classic.Session, JDBCContext.Context { // todo : need to find a clean way to handle the "event source" role
// a seperate classs responsible for generating/dispatching events just duplicates most of the Session methods...
// passing around seperate reto interceptor, factory, actionQueue, and persistentContext is not manageable... private static final Logger log = LoggerFactory.getLogger(SessionImpl.class); private transient EntityMode entityMode = EntityMode.POJO;
private transient boolean autoClear; //for EJB3

private transient long timestamp;
private transient FlushMode flushMode = FlushMode.AUTO;
private transient CacheMode cacheMode = CacheMode.NORMAL; private transient Interceptor interceptor; private transient int dontFlushFromFind = 0; private transient ActionQueue actionQueue;
private transient StatefulPersistenceContext persistenceContext;
private transient JDBCContext jdbcContext;
private transient EventListeners listeners; private transient boolean flushBeforeCompletionEnabled;
private transient boolean autoCloseSessionEnabled;
private transient ConnectionReleaseMode connectionReleaseMode;

private transient String fetchProfile; private transient Map enabledFilters = new HashMap(); private transient Session rootSession;
private transient Map childSessionsByEntityMode; private EntityNameResolver entityNameResolver = new CoordinatingEntityNameResolver();

解决方案 »

  1.   

    DriverManager是可以并发生成Connection的,但同一个Connection并发使用就有可能出问题。
    我是这么认为的。道理和SessionFactory与Session类似。
      

  2.   

    谢谢,我想以SessionFactory和Session实例变量的角度去分析这个问题。因为一般来说非线程安全,是在单例模式下,个个线程访问同一个单例,共享实例变量引发的。那我想知道,在不使用ThreadLocal维护session的情况下。Session是如何非线程安全的。因为它的众多实例变量吗?
    还有SessionFactory,它是线程安全的。但它的实例变量中存在 
    private transient boolean isClosed = false;
    private transient SchemaExport schemaExport;
    不会会影响线程安全?
      

  3.   

    线程同步 与成员变量是否final并不同一回事说SessionFactory线程安全指的是即使多个线程同时调用其中的方法,它都能保证不会出现数据丢失、返回不正确等一系列的问题而Session当同时有多个线程使用时,可能会造成其中一部分的操作丢失,这就是线程不安全。
      

  4.   

    final是不能改变的,当然不存在丢失的情况。所谓的丢失,也就是成员变量的值改变了吧。我认为,还是要具体到成员变量的分析上..SessionFactory能保证不出现数据丢失,从类的代码上仔细分析分析?
      

  5.   

    final与否并不能保证是否是线程安全的
    Session封装了Connection 而Connection是不能被共用的
      

  6.   

    大致可以这样理解DataSource ----> Connection
    SessionFactory -----> Session
      

  7.   

    恩,sessionFactory可以openSession很多嘛,感觉用getCurrentSession()安全点吧?
      

  8.   

    其实我是想要更深入点的回答,session是对应于connection。没错,session不能共享,想想就知道。一个连接肯定不能被多个线程通过时操作啊。openSession是可以open很多,当然明白啦。用ThreadLocal来维护session我也明白。但这些都不是我想要的回答。我始终认为,破坏线程安全(不是线程同步)的根本原因在于类的实例变量。在多个线程拿到同一个对象的引用的情况下。如果这个对象的实例变量可以被改变,那就是非线程安全的。反之,如果一个类没有实例变量或只有final型的实例变量,类的对象是不能改变的,这个类就是线程安全的。我的这个观点对吗?欢迎抛砖....如果没错,就拜托从Session和SessionFactory的实例变量来讨论这个问题!贴子加分啦,欢迎大家热烈讨论。
      

  9.   

    Session session = SessionFactory.getSession();
    这一步加final的意思是session这个引用对象只能指向SessionFactort.getSession()传回的这个对象,之后其指向的对象地址不能再次改变,不加final是可以再次赋值(即再次改变其指向)的。而加不加final,session所指向的这个对象的内在属性是完全可以改变的,甚至可以模拟两个线程,同时调用其方法,改变这个对象的设置。所以加final与线程安全与否几乎没有关系
    方法中的局部变量会随着方法的调用而新建和释放,因此没有线程安全之忧,有担忧的确是是类变量,不过这也要看情况。类的实例对象在正常使用的情况下也不存在线程问题(不同线程不同对象),但某些情况就会有问题,比如servletClass的实例,请求第一次到达后web容器会实例化对应的servletClass,而此后无论再有多少类似请求,都会使用第一次实例化的这个对象(除非长期没有这个请求,web容器有可能会销毁这个对象)而http请求是有并发现象存在的,操作的又是同一个对象,当然会出现并发操作同一个对象中的同一个类变量的问题,这就是线程不安全的一个例子。
    SessionFactory在方法中创建Session,并返回给调用端,当然不存在线程问题,当然能保证为不同地点,不同线程的调用者提供不同的Session,而Session一旦创建,就要看调用者如何使用了,把它当做类变量使用,而又把这个类的实例供多个线程操作,而又不加排它锁,当然会出线程安全的问题。通常在控制单元(servletClass/struts的action)使用hibernateSession或jdbc的connection时,都不建议把它作为类变量来用。
    ------------------
    打的累死了,呼呼,发现自己越来越啰嗦了,但愿是楼主要的答案。
      

  10.   

    自己拍个砖板先...
    同学,我又仔细揣摩了你的话。小小的茅厕顿开了下。我们不能单纯的看他们的实例变量是否final或非final。即使是非final的实例变量,他们也是private的。多个线程操作同一个对象的引用,改变非final变量的值的途径只有通过类的方法来改变。若类的方法中完全不涉及到去改非final的实例变量。那这个非final的实例变量就不会影响线程安全了。对吧?仔细再分析下SessionFactory的源码,只有
    private transient SchemaExport schemaExport;
    private transient boolean isClosed = false;
    比较可疑,看看源代码中哪里用了他们:
    if ( settings.is
    //这是SessioinFactoryImpl的构造函数中的代码
    AutoCreateSchema() ) {
    new SchemaExport( cfg, settings ).create( false, true );
    }
    if ( settings.isAutoUpdateSchema() ) {
    new SchemaUpdate( cfg, settings ).execute( false, true );
    }
    if ( settings.isAutoValidateSchema() ) {
    new SchemaValidator( cfg, settings ).validate();
    }
    if ( settings.isAutoDropSchema() ) {
    schemaExport = new SchemaExport( cfg, settings );
    }从多个线程操作一个SessionFactory角度看,似乎不涉及到schemaExport会被那个线程改掉...public void close() throws HibernateException { if ( isClosed ) {
    log.trace( "already closed" );
    return;
    } log.info("closing"); isClosed = true; Iterator iter = entityPersisters.values().iterator();
    while ( iter.hasNext() ) {
    EntityPersister p = (EntityPersister) iter.next();
    if ( p.hasCache() ) {
    p.getCacheAccessStrategy().getRegion().destroy();
    }
    }这个就有点矛盾了,如果一个线程中调用了SessionFactroy.close()方法,那isClose这个实例变量就改变了。这是非线程安全的啊????如何解释呢????????
      

  11.   

    搞懂java里面对象、变量的生存周期,final关键词的作用,就差不多能解释上面有关线程安全的疑问了,而SessionFactory和Session仅仅是这个问题的一个例子,解决线程安全的疑惑,这个SessionFactory和Session的问题也就理解了。
      

  12.   


    谢谢,辛苦了。你这也只能说明SessionFactory.getSession()是线程安全的,因为这里是new了个session。你也说了,在特定的环境下(单例),类变量影响线程安全的因子。fianl的类变量不可改变,所以我觉得类变量加final与线程安全与否还是有一定关系的。
    我觉得你讲的很严谨,明白了我的意思。其实我这个问题的前提就是类似于ServletClass。说得具体些:就是在单例的Dao层上,单纯把session作为Dao的实例变量,多个线程去共享一个session。这种状态下产生的线程安全问题(当然,这是非常差的代码设计,只是作为例子而已)。这个我敢肯定因为Session中存在大量的可以被改变的实例变量。但是若SessionFactory作为Dao的实例变量,我们不讨论SessionFactory.openSession()。SessionFactory的close()方法如何解释呢?
      

  13.   

    我的一点想法:
      
      首先:我们应该如何判断一个类是不是线程安全的?  举例:Serverlet,楼上已经有人说了,我补充下,这个类是个单例类,即在web容器中只存在一个对象,那么在多线程调用时,就会存在共享同一个实例,同一个类成员函数,成员变量,于是就存在线程不安全的情况,解决办法就要加锁synchronized,必需逐个线程排队使用.  回到话题, SessionFactoryImpl我们是怎么获得的了,它是不是个单例类了,它的方法有没有加锁了?
      同理我么一样思考 SessionImp??
      

  14.   

    首先,我认为你的核心问题是这个:
    “在不使用ThreadLocal维护session的情况下。为什么说Session是非线程安全的。是否是因为它存在很多类级变量吗?如果确实如此的话,那么SessionFactory尽管是是线程安全的。但它同样也存在很多类级的非final成员实例变量,如:private transient boolean isClosed = false;private transient SchemaExport schemaExport;这难道不会影响线程安全么?”
     
    这个问题应该从两个角度说:
    首先,我认为楼主需要弄清楚的是final关键字可以修饰多种目标,final可以修饰非抽象类、非抽象类成员方法和变量。
    如果是修饰类,那么final类不能被继承,没有子类,final类中的方法默认是final的。
    如果是修饰方法,那么final方法不能被子类的方法覆盖,但可以被继承。
    如果是修饰成员变量,那么final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
    另外,final不能用于修饰构造方法。
    由此可见,SF(SessionFactory)中的确已经将绝大部分的成员变量设计成了final的,因此,确实不存在并发冲突的问题,而对仅有的几个非final成员变量的操作也并非在SF类中提供,因此,SF确实是现成安全的。反观Session,transient是和序列化相关的一个修饰符,和线程安全无关,因此,Session中的大部分成员变量都复发避免并非访问冲突的可能。因此,Session确实不能认为是线程安全的。其次,线程安全和线程敏感这两个概念我个人认为应该区分一下,ThreadLocal实现的只是一种线程敏感特性,也就是说,Hibernate2中需要用Util包中的方法获得和线程相关的Session,而到了Hibernate3,尽管SessionFactory中已经整合好了ThreadLocal的特性,这主要是依靠CurrentSessionContext接口的不同实现类(如JPA,Hibernate等实现模式)来从一个大的SessionMap中获得和线程相关的Session(这种Session一般称为线程敏感的),但是,Session自身依然是非线程安全的,原因见前面“首先”部分的分析。这里引入“其次”的分析主要是想避免将线程安全和线程敏感混淆。最后,10楼的哥们第一段和最后一段说的很多,第一段中说的final的位置楼主不要认为是修饰成员变量,他说的final的位置是修饰整个实例对象,这个时候由于实例对象属于引用对象,因此考虑到堆内存和栈内存的关系,实际对象依然可以被不同线程改变,因此,在实例对象前加final修饰符并不能代表这个对象就是线程安全的。
    10楼的最后一段说的也很好,其实之所以说要认识到Session是非线程安全的,主要是提醒大家,在自己开发的调用类中,不要将Session设置为成员变量,而是要将Session写在方法中,这样得到的Session才是因线程而异的,如果放在类中,只随着Servlet初始化时构建一次,那么今后并发冲突时肯定难免的了。打了这么多,还挺累的
      

  15.   

    16楼兄弟,感激不尽啊!最后一个疑问,你说:
    而对仅有的几个非final成员变量的操作也并非在SF类中提供,因此,SF确实是现成安全的。那解释下我在11楼提到的SessionFactory的 isClosed 变量,对isClose的操作在SF中提供了啊? 
      

  16.   

    16楼兄弟对final的理解很到位!
      

  17.   

    SessionFactoryImpl 显然不是严格语义上的thread safe,那么多getXXX()都暴露在外面,客户程序可以随意put。
    为什么java doc里面说的“It is crucial that the class is not only thread safe, but also highly concurrent.” ?
    那是因为有些变量虽然可以改,但是不会影响功能,因为在constructor的时候变量已经从Configuration里面解析好了,譬如:SessionFactoryImpl.setting。
    再者有些变量已经处理过了,你get了也没法改。譬如:SessionFactoryImpl.classMetadata,在constructor的时候已经classMetadata = Collections.unmodifiableMap(classMeta);了。
      

  18.   

    isClose()确实被SF实现类提供,是暴露的,但是,不是说一个类暴露了变量,那么这个类就一定是不安全的,这里所谓的安全主要是指是否能有效控制并发。而所谓并发控制首先需要明确的是边界,并发控制的边界我认为是:"从你执行‘读’到你执行‘写’这一过程中有没有其它的人‘写入’?" 由于SF的open与close基本上是托管给容器的,因此这个变量的状态不是一个线程可以改变的,所以,这样的变量并不涉及并发冲突。因此也不影响线程安全。
    另外,并发控制一般靠锁来实现,他的核心思想是解决‘读’完之后谁能‘写’,而事物隔离级别解决的是‘写’完之后谁能‘读’。他们分别体现的是ACID中的C和I,是不同的概念。有没有其它的人写?”
    综上,一个暴露的变量如果从功能设计上的目的只是为了提供‘读取’的目的,那么即使暴露也不会产生任何预期的并发冲突。
    这几天忙着去北京申请访问学者的签证,所以没有来回帖,不好意思。
      

  19.   

    另外,楼主可以借鉴21楼兄弟的表述,基本上是正确的,也就是说,‘写入’这个功能不是线程提供的,而是构造方法,托管容器,甚至Aspect功能来实现的。