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

taor4pac  于 2023-02-28  发布在  Java
关注(0)|答案(8)|浏览(159)

如果我有一个util类,它的静态方法将调用Hibernate函数来完成基本的数据访问,我想知道创建方法synchronized是否是确保线程安全的正确方法。
我希望这样可以防止信息被访问到相同的DB示例。但是,我现在可以确定下面的代码是否可以防止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
}
x33g5p2x

x33g5p2x1#

为了更全面地回答这个问题...
请记住,在方法上使用synchronized实际上只是一种简化(假设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) {...}
    }
}

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

up9lanfz

up9lanfz2#

通过在静态方法锁上使用synchronized,您将同步类方法和属性(与示例方法和属性相对)
所以你的假设是正确的。

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

不完全是。你应该让你的关系数据库管理系统来做那项工作。他们很擅长这类事情。
同步数据库访问的唯一好处是会让应用程序变得非常慢。此外,在你发布的代码中,你每次都在构建一个会话工厂,这样,你的应用程序访问数据库的时间会比执行实际任务的时间多。
想象一下下面的场景:
客户机A和B试图向表T的记录X插入不同的信息。
使用你的方法,你得到的唯一的东西是确保一个在另一个之后被调用,当这种情况无论如何都会在DB中发生时,因为RDBMS将阻止他们同时插入来自A的一半信息和来自B的一半信息,结果将是相同的,但只是慢了5倍(或更多)。
也许看一下Hibernate文档中的"Transactions and Concurrency"章节会更好,大多数时候你试图解决的问题已经解决了,而且是一个更好的方法。

vxf3dgd4

vxf3dgd43#

静态方法使用类作为锁的对象,在你的例子中是Utils.class,所以是的,这是可以的。

s8vozzvw

s8vozzvw4#

static synchronized意味着在类的Class对象上持有锁,而synchronized意味着在类的示例上持有锁。这意味着,如果你在一个线程(执行)中访问一个非静态同步方法,你仍然可以使用另一个线程访问一个静态同步方法。
因此,在任何时间点由多个线程访问两个相同类型的方法(两个静态或两个非静态方法)是不可能的。

9lowa7mx

9lowa7mx5#

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

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

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

pkln4tw6

pkln4tw66#

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

flmtquvp

flmtquvp7#

回答你的问题,是的:你的synchronized方法不能被一个以上的线程同时执行.

omjgkv6w

omjgkv6w8#

synchronized Java关键字的工作原理

向静态方法添加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的多线程访问。
您需要做的是在将更改应用到DB时使用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的情况下,我回滚了事务

相关问题