在 Java 中,同步的静态方法是如何工作的? 我可以用它来加载 Hibernate 实体吗?

如果我有一个带有静态方法的 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
}
202135 次浏览

静态方法使用该类作为锁定的对象,对于您的示例,该对象是 Utils.class。所以,是的,没问题。

如果它与数据库中的数据有关,为什么不利用数据库隔离锁来实现呢?

要回答您的问题,是的: 您的 synchronized方法一次不能由多个线程执行。

为什么要强制在任何时候只有一个线程可以访问数据库?

实现任何必要的锁定是数据库驱动程序 的工作,假设一个 Connection一次只被一个线程使用!

最有可能的情况是,您的数据库完全能够处理多重并行访问

通过在静态方法锁上使用 synized,您将获得 同步类方法和属性(与实例方法和属性相反)

所以你的假设是正确的。

我想知道使方法同步是否是确保线程安全的正确方法。

其实不然。你应该让你的关系数据库管理系统来做这些工作。他们很擅长这类事情。

通过同步对数据库的访问,您将得到的唯一结果是使您的应用程序非常慢。此外,在您发布的代码中,您每次都在构建会话工厂,这样,您的应用程序将花费比执行实际作业更多的时间来访问数据库。

想象一下下面的场景:

客户机 A 和 B 试图向表 T 的记录 X 中插入不同的信息。

在您的方法中,您得到的唯一一件事情就是确保一个接一个地调用,而这在数据库中无论如何都会发生,因为 RDBMS 将阻止它们同时插入来自 A 和来自 B 的一半信息。结果将是相同的,但只有5倍(或更多)慢。

也许在 Hibernate 文档中查看 “事务和并发”一章会更好。大多数时候,你试图解决的问题,已经被解决了,而且是一个更好的方法。

为了更全面地回答这个问题..。

请记住,在方法上使用 synized 实际上只是一种速记(假设 class 是 Some Class) :

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) {...}
}
}

(对于非静态方法,您希望将锁设置为非静态字段)

static synchronized意味着锁定类的 Class对象 在哪里 synchronized意味着锁定该类的对象本身。这意味着,如果您正在访问线程中的非静态同步方法,您仍然可以使用另一个线程访问静态同步方法。

因此,在任何时间点,通过多个线程访问两个相同类型的方法(两个静态方法或两个非静态方法)是不可能的。

synchronizedJava 关键字是如何工作的

synchronized关键字添加到静态方法时,该方法一次只能由单个线程调用。

在您的情况下,每个方法调用都将:

  • 创建一个新的 SessionFactory
  • 创建一个新的 Session
  • 获取实体
  • 将实体返回给调用方

然而,这些是你的要求:

  • 我希望这样可以防止访问同一数据库实例的信息。
  • 防止 getObjectById在被特定类调用时被所有类调用

因此,即使 getObjectById方法是线程安全的,实现也是错误的。

SessionFactory最佳实践

SessionFactory是线程安全的,创建它是一个非常昂贵的对象,因为它需要解析实体类并构建内部实体元模型表示。

因此,不应该在每个 getObjectById方法调用上创建 SessionFactory

相反,您应该为它创建一个单例实例。

private static final SessionFactory sessionFactory = new Configuration()
.configure()
.buildSessionFactory();

Session应该总是关闭的

您没有关闭 finally块中的 Session,如果在加载实体时抛出异常,这可能会泄漏数据库资源。

根据 Session.load方法 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 的多线程访问。

您需要做的是在向数据库应用更改时使用 ABC0或 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的情况下,我回滚了事务