ChatGPT解决这个技术问题 Extra ChatGPT

同步静态方法在 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
}

S
Scott Stanchfield

为了更普遍地解决这个问题......

请记住,在方法上使用同步实际上只是简写(假设类是 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) {...}
    }
}

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


前 4 个代码块是黄金。正是我想要的。谢谢你。
如果我在非静态方法上使用静态锁,那么 SomeClass 类的任何两个对象都不能同时运行该块,这是否正确?
@Samuel - 几乎......它更多的是关于线程而不是对象实例。您是正确的, SomeClass 的单独实例都将使用相同的锁/监视器:与 Someclass.class 对象关联的那个。因此,如果两个不同的线程正在处理 SomeClass 的两个不同实例,它们就不能同时运行。但是,如果单个线程在 SomeClass 的一个实例中调用方法,而该方法在另一个实例中调用方法,则不会发生阻塞。
@ScottStanchfield您列出了同步方法的方法,它们都是等效的吗?
@Bionix1441 - 一切都与范围有关。上面的每个机制都可以让您更好地控制锁定。首先,使用实例本身锁定整个方法,然后使用实例本身锁定方法内的部分,然后使用任何对象实例锁定部分。
C
Community

通过在静态方法锁上使用同步,您将synchronize the class methods and attributes(与实例方法和属性相反)

所以你的假设是正确的。

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

并不真地。您应该让您的 RDBMS 代替它来完成这项工作。他们擅长这种东西。

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

想象以下场景:

客户端 A 和 B 尝试将不同的信息插入到表 T 的记录 X 中。

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

可能最好看一下 Hibernate 文档中的 "Transactions and Concurrency" 章。大多数时候,你试图解决的问题已经解决了,而且是一种更好的方法。


非常有帮助的答案!谢谢!因此,Hibernate 通过“乐观锁定”来处理并发性。那么根本不需要使用“同步”方法来解决任何数据访问并发?仅当数据未存储在数据库中时才使用“同步”方法? ..你什么时候使用它们?
1)我认为也有一些方法可以使用悲观锁定。 2) 不,RDBMS 可以完成这项工作。 3)如果数据被多个线程同时访问。 4)当两个线程必须共享数据时,同步很有用。如果他们不需要,那就更好了!
任何快餐店都使用多线程。一个线程带您订购并使用另一个线程来准备它,并继续与下一位客户联系。同步点仅在它们交换信息以知道要准备什么时才起作用。遵循这样的模式确实可以简化生活。
“全班”没有被锁定。 Java machine language specification: For a class (static) method, the monitor associated with the Class object for the method's class is used. For an instance method, the monitor associated with this (the object for which the method was invoked) is used. 因此,如果一个线程进入一个静态方法,由 Object#getClass 返回的同一 object 将被锁定。其他线程仍然可以访问实例方法。
大声笑我发现我自己的措辞最终也不是正确的。我说“因此,如果一个线程进入静态方法,则 Object#getClass 返回的同一对象被锁定”。技术上不正确。对于所有好奇的人来说,长话短说:对于应用程序中的每个类,都有一个 Class 对象,由其中一个虚拟机类加载器实例化。与所有对象一样,该对象也有一个与之关联的 Monitor。而这个 monitor 就是被锁定的东西。
s
starblue

静态方法使用类作为锁定对象,例如 Utils.class。所以是的,没关系。


p
prasad

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

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


o
oxbow_lakes

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

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

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


我打赌它是/曾经是某些交易问题的解决方法。即,解决方案不能解决真正的问题
我不知道......我想我必须手动实现这个。感谢您指出! :)
R
Ray Lu

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


我没有任何数据库背景。现在我知道了!!感谢您指出! :)
D
David Z

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


V
Vlad Mihalcea

synchronized Java 关键字的工作原理

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

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

创建一个新的会话工厂

创建一个新会话

获取实体

将实体返回给调用者

但是,这些是您的要求:

我希望这可以防止访问同一数据库实例的信息。

防止在特定类调用 getObjectById 时为所有类调用它

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

SessionFactory 最佳实践

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

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

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

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

会话应始终关闭

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

如果在数据库中找不到实体,则根据 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 实体并提交了交易

在抛出异常的情况下,我回滚了事务