【发布时间】:2014-11-03 18:37:29
【问题描述】:
我的以下代码面临与休眠会话的同步问题。我的代码中几乎没有并行线程,每个线程都有自己的休眠会话。问题是,由于某些未知原因,其他会话无法感知到某个会话所做的更改。代码位于githubhere
问题:
这里我用三个线程来解释:PRODUCER、CONSUMER_1、CONSUMER_2。 CONSUMER_1 等待生产者完成其工作,即使在那之后,最后,它也看不到生产者线程所做的更改。为什么会这样?
package org.example.hibernate;
import org.example.hibernate.model.User;
import org.example.hibernate.util.HibernateUtil;
import java.util.Random;
public class Main {
/**
* This object acts as synchronisation semaphore between threads.
* (Note : aware that wait within hibernate session is discouraged)
* Here it is used to show that the consumer tries to read/get after
* producer has successfully completed the transaction.
* So here, the producer notifies waiting threads with this object
*/
public static final Object LOCK = new Object();
/**
* user Id is primary key, a random int is suffixed to preserve uniqueness
* Here, Producer saves an Object of this ID, then consumer tries to read it
*/
private static final String USER_ID = "user-" + new Random().nextInt(10000);
/**
* This is producer thread, it inserts a record and notifies about it to
* other waiting threads.
*/
private static Thread PRODUCER = new Thread("producer") {
// this this creates a user and notifies threads waiting for some event
@Override
public void run() {
HibernateUtil.getInstance().executeInSession(new Runnable() {
@Override
public void run() {
User user = new User();
user.setId(USER_ID);
user.setName("name-" + USER_ID);
user.save();
}
});
// outside the session
synchronized (LOCK) {
print("Notifying all consumers");
LOCK.notifyAll();
}
print("dying...");
}
};
/**
* This thread tries to read first, if it misses, then waits for the producer to
* notify, after it receives notification it tries to read again
*/
private static Thread CONSUMER_1 = new Thread("consumer_one"){
// this thread checks if data available(user with specific ID),
// if not available, waits for the the producer to notify it
@Override
public void run() {
HibernateUtil.getInstance().executeInSession(new Runnable() {
@Override
public void run() {
try {
User readUser = User.getById(USER_ID);
if(readUser == null) { // data not available
synchronized (LOCK) {
print("Data not available, Waiting for the producer...");
LOCK.wait(); // wait for the producer
print("Data available");
}
print("waiting for some more time....");
Thread.sleep(2 * 1000);
print("Enough of waiting... now going to read");
}
readUser = User.getById(USER_ID);
if(readUser == null) {
// why does this happen??
throw new IllegalStateException(
Thread.currentThread().getName()
+ " : This shouldn't be happening!!");
} else {
print("SUCCESS: Read user :" + readUser);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
print("dying...");
}
};
/**
* this thread waits for the the producer to notify it, then tries to read
*/
private static Thread CONSUMER_2 = new Thread("consumer_two"){
@Override
public void run() {
HibernateUtil.getInstance().executeInSession(new Runnable() {
@Override
public void run() {
try {
synchronized (LOCK) {
print("Data not available, Waiting for the producer...");
LOCK.wait(); // wait for the producer notification
print("Data available");
}
print("waiting for some more time....");
Thread.sleep(2 * 1000);
print("Enough of waiting... now going to read");
User readUser = User.getById(USER_ID);
if(readUser == null) {
throw new IllegalStateException(
Thread.currentThread().getName() +
" : This shouldn't be happening!!");
} else {
print("SUCCESS :: Read user :" + readUser);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
print("dying...");
}
};
/**
* Just another print method to include time stamp and thread name
* @param msg
*/
public static void print(String msg) {
System.out.println(Thread.currentThread().getName() + " : "
+ System.currentTimeMillis()+ " : "+ msg);
}
public static void main(String[] args) throws InterruptedException {
// Initialise hibernate in main thread
HibernateUtil.getInstance();
PRODUCER.start();
CONSUMER_1.start();
CONSUMER_2.start();
PRODUCER.join();
CONSUMER_1.join();
CONSUMER_2.join();
print("Exiting....");
}
}
还有输出:
INFO: HHH000232: Schema update complete
[main] INFO org.example.hibernate.util.HibernateUtil - Hibernate Initialised..
consumer_two : 1415036718712 : Data not available, Waiting for the producer...
[producer] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
consumer_one : 1415036718831 : Data not available, Waiting for the producer...
[producer] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
producer : 1415036718919 : Notifying all consumers
producer : 1415036718919 : dying...
consumer_one : 1415036718919 : Data available
consumer_one : 1415036718919 : waiting for some more time....
consumer_two : 1415036718919 : Data available
consumer_two : 1415036718919 : waiting for some more time....
[producer] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
consumer_one : 1415036720919 : Enough of waiting... now going to read
consumer_two : 1415036720920 : Enough of waiting... now going to read
Nov 03, 2014 11:15:20 PM com.mchange.v2.c3p0.stmt.GooGooStatementCache assimilateNewCheckedOutStatement
INFO: Multiply prepared statement! select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from user user0_ where user0_.id=?
java.lang.IllegalStateException: consumer_one : This shouldn't be happening!!
at org.example.hibernate.Main$2$1.run(Main.java:79)
at org.example.hibernate.util.HibernateUtil.executeInSession(HibernateUtil.java:60)
at org.example.hibernate.Main$2.run(Main.java:61)
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
consumer_one : 1415036720931 : dying...
consumer_two : 1415036720940 : SUCCESS :: Read user :User{id='user-422', name='name-user-422'} org.example.hibernate.model.User@4666d804
consumer_two : 1415036720943 : dying...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
main : 1415036720943 : Exiting....
这是我的休眠配置:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration SYSTEM "classpath://org/hibernate/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hib_ex</property>
<property name="connection.username">hibuser</property>
<property name="connection.password">hibpass</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">10</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">1800</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="connection.provider_class"> org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.use_query_cache">false</property>
<property name="hibernate.cache.use_minimal_puts">true</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.internal.NoCachingRegionFactory</property>
<property name="show_sql">false</property>
<mapping class="org.example.hibernate.model.User" />
</session-factory>
休眠实用程序
public enum HibernateUtil {
INSTANCE;
private final Logger LOG = LoggerFactory.getLogger(HibernateUtil.class);
private final String CONFIG_FILE = "hibernate.xml";
private final SessionFactory sessionFactory;
HibernateUtil(){
LOG.info("Initialising hibernate...");
URL configUrl = getClass().getClassLoader().getResource(CONFIG_FILE);
final Configuration configuration = new Configuration();
try {
configuration.configure(configUrl);
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
LOG.info("Hibernate Initialised..");
} catch (Exception e){
throw new IllegalStateException("Could not init hibernate!");
}
}
public Session getSession(){
if(sessionFactory.getCurrentSession() != null
&& sessionFactory.getCurrentSession().isOpen()) {
return sessionFactory.getCurrentSession();
} else {
LOG.info("Opening a session");
return sessionFactory.openSession();
}
}
public void executeInSession(Runnable runnable){
Session session = getSession();
Transaction transaction = session.getTransaction();
if(!transaction.isActive()){
LOG.info("Starting the transaction...");
transaction.begin();
}
try {
runnable.run();
} catch (Exception e){
e.printStackTrace();
} finally {
if(transaction.isActive()) {
LOG.info("Committing the transaction...");
transaction.commit();
} else {
LOG.info("Transaction was committed...");
}
if(session.isOpen()){
LOG.info("Closing the session...");
session.close();
} else {
LOG.info("Session was closed...");
}
}
}
public static HibernateUtil getInstance(){
return INSTANCE;
}
}
请帮助我理解:
- 为什么 CONSUMER_1 线程的 User.getById(userId) 会在 PRODUCER 线程的事务成功完成后返回 null?
- 当 CONSUMER_1 变空时,CONSUMER_2 线程的User.getById(userId) 是如何几乎同时获得同一个对象的?
为了节省您的宝贵时间,请从github repo获取完整代码
【问题讨论】:
标签: multithreading hibernate session