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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/578904/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:27:55  来源:igfitidea点击:

How do synchronized static methods work in Java and can I use it for loading Hibernate entities?

javamultithreadinghibernateconcurrencysynchronization

提问by tomato

If I have a util class with static methods that will call Hibernate functions to accomplish basic data access. I am wondering if making the method synchronizedis the right approach to ensure thread-safety.

如果我有一个带有静态方法的 util 类,它将调用 Hibernate 函数来完成基本的数据访问。我想知道制作该方法synchronized是否是确保线程安全的正确方法。

I want this to prevent access of info to the same DB instance. However, I'm now sure if the following code are preventing getObjectByIdbeing called for all Classes when it is called by a particular class.

我希望这样可以防止访问同一数据库实例的信息。但是,我现在确定以下代码在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
}

采纳答案by OscarRyz

By using synchronized on a static method lock you will synchronize the class methods and attributes( as opposed to instance methods and attributes )

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

So your assumption is correct.

所以你的假设是正确的。

I am wondering if making the method synchronized is the right approach to ensure thread-safety.

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

Not really. You should let that work do your RDBMS instead. They are good at this kind of stuff.

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

The only thing you will get by synchronizing the access to the database is to make your application terribly slow. Further more, in the code you posted you're building a Session Factory each time, that way, your application will spend more time accessing the DB than performing the actual job.

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

Imagine the following scenario:

想象以下场景:

Client A and B attempt to insert different information into record X of table T.

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

With your approach the only thing you're getting is to make sure one is called after the other, when this would happen anyway in the DB, because the RDBMS will prevent them from inserting half information from A and half from B at the same time. The result will be the same but only 5 times ( or more ) slower.

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

Probably it could be better to take a look at the "Transactions and Concurrency"chapter in the Hibernate documentation. Most of the times the problems you're trying to solve, have been solved already and a much better way.

看一下Hibernate 文档中的“事务和并发”一章可能会更好。大多数情况下,您试图解决的问题已经得到解决,而且是一种更好的方法。

回答by starblue

Static methods use the class as the object for locking, which is Utils.class for your example. So yes, it is OK.

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

回答by Ray Lu

If it is something to do with the data in your database, why not utilize database isolation locking to achieve?

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

回答by David Z

To answer your question, yes it does: your synchronizedmethod cannot be executed by more than one thread at a time.

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

回答by oxbow_lakes

Why do you want to enforce that only a single thread can access the DB at any one time?

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

It is the job of the database driverto implement any necessary locking, assuming a Connectionis only used by one thread at a time!

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

Most likely, your database is perfectly capable of handling multiple, parallel access

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

回答by Scott Stanchfield

To address the question more generally...

为了更一般地解决这个问题......

Keep in mind that using synchronized on methods is really just shorthand (assume class is SomeClass):

请记住,在方法上使用同步实际上只是速记(假设类是 SomeClass):

synchronized static void foo() {
    ...
}

is the same as

是相同的

static void foo() {
    synchronized(SomeClass.class) {
        ...
    }
}

and

synchronized void foo() {
    ...
}

is the same as

是相同的

void foo() {
    synchronized(this) {
        ...
    }
}

You can use any object as the lock. If you want to lock subsets of static methods, you can

您可以使用任何对象作为锁。如果你想锁定静态方法的子集,你可以

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

(for non-static methods, you would want to make the locks be non-static fields)

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

回答by prasad

static synchronizedmeans holding lock on the the class's Classobject where as synchronizedmeans holding lock on that class's object itself. That means, if you are accessing a non-static synchronized method in a thread (of execution) you still can access a static synchronized method using another thread.

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

So, accessing two same kind of methods(either two static or two non-static methods) at any point of time by more than a thread is not possible.

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

回答by Vlad Mihalcea

How the synchronizedJava keyword works

如何在synchronizedJava的关键字作品

When you add the synchronizedkeyword to a static method, the method can only be called by a single thread at a time.

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

In your case, every method call will:

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

  • create a new SessionFactory
  • create a new Session
  • fetch the entity
  • return the entity back to the caller
  • 创建一个新的 SessionFactory
  • 创建一个新的 Session
  • 获取实体
  • 将实体返回给调用者

However, these were your requirements:

但是,这些是您的要求:

  • I want this to prevent access to info to the same DB instance.
  • preventing getObjectByIdbeing called for all classes when it is called by a particular class
  • 我希望这样可以防止访问同一数据库实例的信息。
  • 防止getObjectById在特定类调用时被所有类调用

So, even if the getObjectByIdmethod is thread-safe, the implementation is wrong.

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

SessionFactorybest practices

SessionFactory最佳实践

The SessionFactoryis thread-safe, and it's a very expensive object to create as it needs to parse the entity classes and build the internal entity metamodel representation.

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

So, you shouldn't create the SessionFactoryon every getObjectByIdmethod call.

所以,你不应该SessionFactory在每个getObjectById方法调用上创建。

Instead, you should create a singleton instance for it.

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

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

The Sessionshould always be closed

Session应始终关闭

You didn't close the Sessionin a finallyblock, and this can leak database resources if an exception is thrown when loading the entity.

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

According to the Session.loadmethod JavaDocmight throw a HibernateExceptionif the entity cannot be found in the database.

根据Session.load方法 JavaDoc可能会抛出 aHibernateException如果在数据库中找不到实体。

You should not use this method to determine if an instance exists (use get()instead). Use this only to retrieve an instance that you assume exists, where non-existence would be an actual error.

您不应使用此方法来确定实例是否存在(get()改为使用)。仅用于检索您假定存在的实例,其中不存在将是实际错误。

That's why you need to use a finallyblock to close the Session, like this:

这就是为什么您需要使用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(); 
         }
     }
 }

Preventing multi-thread access

防止多线程访问

In your case, you wanted to make sure only one thread gets access to that particular entity.

在您的情况下,您希望确保只有一个线程可以访问该特定实体。

But the synchronizedkeyword only prevents two threads from calling the getObjectByIdconcurrently. If the two threads call this method one after the other, you will still have two threads using this entity.

但是synchronized关键字只能防止两个线程getObjectById同时调用。如果两个线程一个接一个调用这个方法,你仍然会有两个线程使用这个实体。

So, if you want to lock a given database object so no other thread can modify it, then you need to use database locks.

因此,如果您想锁定给定的数据库对象,以便其他线程无法修改它,那么您需要使用数据库锁。

The synchronizedkeyword only works in a single JVM. If you have multiple web nodes, this will not prevent multi-thread access across multiple JVMs.

synchronized关键字仅适用于单个 JVM。如果您有多个 Web 节点,这不会阻止跨多个 JVM 的多线程访问。

What you need to do is use LockModeType.PESSIMISTIC_READor LockModeType.PESSIMISTIC_WRITEwhile applying the changes to the DB, like this:

您需要做的是使用LockModeType.PESSIMISTIC_READLockModeType.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(); 
    }
}

So, this is what I did:

所以,这就是我所做的:

  • I created a new EntityTransactionand started a new database transaction
  • I loaded the Postentity while holding a lock on the associated database record
  • I changed the Postentity and committed the transaction
  • In the case of an Exceptionbeing thrown, I rolled back the transaction
  • 我创建了一个新的EntityTransaction并开始了一个新的数据库事务
  • Post在关联数据库记录上持有锁的同时加载了实体
  • 我更改了Post实体并提交了交易
  • Exception被抛出的情况下,我回滚了事务

For more details about ACID and database transactions, check out this articleas well.

有关 ACID 和数据库事务的更多详细信息,还可以查看这篇文章