如果我有一个带有静态方法的util类,它将调用Hibernate函数来完成基本数据访问。我想知道方法synchronized
是否是确保线程安全的正确方法。
我希望这可以防止将信息访问到同一个数据库实例。但是,我现在确定以下代码是否阻止在特定类调用所有类时调用getObjectById
。
public class Utils {
public static synchronized Object getObjectById (Class objclass, Long id) {
// call hibernate class
Session session = new Configuration().configure().buildSessionFactory().openSession();
Object obj = session.load(objclass, id);
session.close();
return obj;
}
// other static methods
}
答案 0 :(得分:221)
更广泛地解决这个问题......
请记住,使用synchronized on方法实际上只是简写(假设class是SomeClass):
synchronized static void foo() {
...
}
与
相同static void foo() {
synchronized(SomeClass.class) {
...
}
}
和
synchronized void foo() {
...
}
与
相同void foo() {
synchronized(this) {
...
}
}
您可以使用任何对象作为锁。如果要锁定静态方法的子集,可以
class SomeClass {
private static final Object LOCK_1 = new Object() {};
private static final Object LOCK_2 = new Object() {};
static void foo() {
synchronized(LOCK_1) {...}
}
static void fee() {
synchronized(LOCK_1) {...}
}
static void fie() {
synchronized(LOCK_2) {...}
}
static void fo() {
synchronized(LOCK_2) {...}
}
}
(对于非静态方法,您可能希望将锁定为非静态字段)
答案 1 :(得分:134)
通过在静态方法锁上使用synchronized,您将synchronize the class methods and attributes(而不是实例方法和属性)
所以你的假设是正确的。
我想知道是否使方法同步是确保线程安全的正确方法。
不是真的。您应该让这项工作代替您的RDBMS。他们擅长这种东西。
通过同步对数据库的访问,您将获得的唯一一件事就是使您的应用程序非常慢。此外,在您发布的代码中,您每次都在构建会话工厂,这样,您的应用程序将花费更多时间访问数据库,而不是执行实际工作。
想象一下以下场景:
客户A和B尝试将不同的信息插入表T的记录X中。
使用你的方法你唯一得到的就是确保一个接一个地被调用,当这在DB中发生时,因为RDBMS将阻止它们从A中插入一半信息而在B中插入一半信息。同一时间。结果将相同,但只会慢5倍(或更多)。
看看Hibernate文档中的"Transactions and Concurrency"章节可能会更好。大多数时候,你试图解决的问题已经解决,并且已经有了更好的解决方法。
答案 2 :(得分:17)
静态方法使用类作为锁定对象,例如Utils.class。是的,没关系。
答案 3 :(得分:14)
static synchronized
表示对类的Class
对象进行锁定
在哪里
synchronized
表示锁定该类的对象本身。这意味着,如果要访问(执行)线程中的非静态同步方法,您仍然可以使用另一个线程访问静态同步方法。
因此,不可能在任何时间点通过多个线程访问两种相同类型的方法(两种静态方法或两种非静态方法)。
答案 4 :(得分:9)
为什么要强制执行一次只有一个线程可以访问数据库?
数据库驱动程序的工作是实现任何必要的锁定,假设Connection
一次仅由一个线程使用!
最有可能的是,您的数据库完全能够处理多个并行访问
答案 5 :(得分:2)
如果它与数据库中的数据有关,为什么不利用数据库隔离锁定来实现?
答案 6 :(得分:2)
要回答您的问题,是的确如此:您的synchronized
方法一次不能由多个线程执行。
答案 7 :(得分:2)
synchronized
Java关键字如何工作将synchronized
关键字添加到静态方法时,该方法一次只能由单个线程调用。
对于您而言,每个方法调用都会:
SessionFactory
Session
但是,这些是您的要求:
getObjectById
时为所有类调用因此,即使getObjectById
方法是线程安全的,实现也是错误的。
SessionFactory
最佳做法 SessionFactory
是线程安全的,并且创建它是一个非常昂贵的对象,因为它需要解析实体类并构建内部实体元模型表示形式。
因此,您不应在每个SessionFactory
方法调用中都创建getObjectById
。
相反,您应该为其创建一个单例实例。
private static final SessionFactory sessionFactory = new Configuration()
.configure()
.buildSessionFactory();
Session
应该始终关闭您没有在Session
块中关闭finally
,如果在加载实体时引发异常,这可能会泄漏数据库资源。
如果在数据库中找不到该实体,则根据Session.load
method JavaDoc可能会抛出一个HibernateException
。
您不应使用此方法来确定实例是否存在(改为使用
get()
)。仅用于检索假定为存在的实例,其中不存在将是实际错误。
这就是为什么您需要使用finally
块来关闭Session
的原因,像这样:
public static synchronized Object getObjectById (Class objclass, Long id) {
Session session = null;
try {
session = sessionFactory.openSession();
return session.load(objclass, id);
} finally {
if(session != null) {
session.close();
}
}
}
在您的情况下,您想确保只有一个线程可以访问该特定实体。
但是synchronized
关键字仅阻止两个线程同时调用getObjectById
。如果两个线程一个接一个地调用此方法,则仍然会有两个线程使用此实体。
因此,如果您想锁定一个给定的数据库对象,因此没有其他线程可以修改它,那么您就需要使用数据库锁定。
synchronized
关键字仅在单个JVM中有效。如果您有多个Web节点,则不会阻止跨多个JVM的多线程访问。
您需要做的是在将更改应用于数据库时使用LockModeType.PESSIMISTIC_READ
or LockModeType.PESSIMISTIC_WRITE
,如下所示:
Session session = null;
EntityTransaction tx = null;
try {
session = sessionFactory.openSession();
tx = session.getTransaction();
tx.begin();
Post post = session.find(
Post.class,
id,
LockModeType.LockModeType.PESSIMISTIC_READ
);
post.setTitle("High-Performance Java Perisstence");
tx.commit();
} catch(Exception e) {
LOGGER.error("Post entity could not be changed", e);
if(tx != null) {
tx.rollback();
}
} finally {
if(session != null) {
session.close();
}
}
所以,这就是我所做的:
EntityTransaction
,并开始了新的数据库事务Post
实体Post
实体并提交了交易Exception
的情况下,我回滚了交易有关ACID和数据库事务的更多详细信息,请同时查看this article。