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


181

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

Answers:


137

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

所以你的假设是正确的。

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

并不是的。您应该让RDBMS来完成该工作。他们擅长这类东西。

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

想象以下情况:

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

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

也许最好看看Hibernate文档中的“事务和并发性”一章。在大多数情况下,您要解决的问题已经得到解决,并且是一种更好的方法。


1
非常有帮助的答案!谢谢!因此,Hibernate通过“乐观锁定”来保护Cnocurrency。那么根本不需要使用“同步”方法来解决任何数据访问并发问题?仅当数据未存储在数据库中时才使用“同步”方法?..您何时使用它们?
番茄

1
1)我认为也有一些使用悲观锁定的方法。2)不,RDBMS可以完成这项工作。3)如果多个线程同时访问数据。4)当两个线程必须共享数据时,同步很有用。如果他们不需要,那就更好了!
OscarRyz

7
任何快餐店都使用多线程。一个线程会接您的订单,并使用另一个线程来进行准备,然后继续与下一个客户联系。同步点仅在他们交换信息以知道要准备什么时才起作用。遵循这样的模型确实可以简化生活。
OscarRyz

5
“全班”锁定。该爪哇机器语言规范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.因此,如果一个线程进入一个静态方法中,相同的对象返回的对象#的getClass被锁定。其他线程仍可以访问实例方法。
Martin Andersson

4
大声笑,我发现我自己的措辞也并非最终正确。我说:“因此,如果一个线程进入静态方法,则Object#getClass返回的同一对象将被锁定”。从技术上讲不正确。总而言之,对于所有好奇的人来说简而言之:对于您的应用程序中的每个类,都有一个Class由虚拟机类加载器实例化的对象。像所有对象一样,该对象也具有Monitor关联。而此监视器就是被锁定的。
马丁·安德森

236

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

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

(对于非静态方法,您希望使锁成为非静态字段)


9
前四个代码块是黄金。正是我想要的。谢谢。
Ryan Shillington

如果我在非静态方法上使用静态Lock,是正确的,那么SomeClass类的两个对象将无法同时运行该块吗?
塞缪尔

2
@Samuel-差不多...它更多是关于线程而不是对象实例。您是对的,因为SomeClass的单​​独实例都将使用相同的锁/监视器:与Someclass.class对象关联的锁/监视器。因此,如果两个不同的线程正在处理SomeClass的两个不同实例,则它们都不能同时运行。但是,如果单个线程在SomeClass的一个实例中调用一个方法,而在另一实例中该方法调用一个方法,则不会发生阻塞。
Scott Stanchfield

@ScottStanchfield您已经列出了同步方法的方法,它们都是等效的吗?
Bionix1441

1
@ Bionix1441-全部与范围界定有关。上面的每种机制都可以使您更好地控制锁定。首先,使用实例本身锁定整个方法,然后使用实例本身锁定方法中的部分,然后使用任何对象实例锁定部分。
Scott Stanchfield,

17

静态方法使用该类作为锁定对象,本例中为Utils.class。是的,可以。


14

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

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


10

为什么要强制一次只有一个线程可以随时访问数据库?

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

您的数据库很可能完全能够处理多个并行访问


我敢打赌这是/正在解决某些事务性问题。即,解决方案不能解决真正的问题
马特b

1
我不知道。。。我以为我必须手动执行此操作。感谢您指出!:)
番茄

2

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


我没有任何数据库背景。现在我明白了!!感谢您指出!:)
番茄

2

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


2

如何在synchronizedJava的关键字作品

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

就您而言,每个方法调用将:

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

但是,这些是您的要求:

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

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

SessionFactory 最佳做法

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

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

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

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

Session应始终关闭

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

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

您不应使用此方法来确定实例是否存在(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_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(); 
    }
}

所以,这就是我所做的:

  • 我创建了EntityTransaction一个新数据库,并开始了一个新的数据库事务
  • Post在按住相关数据库记录的锁的同时加载了实体
  • 我更改了Post实体并提交了交易
  • Exception被抛出的情况下,我回滚了交易

有关ACID和数据库事务的更多详细信息,也请参阅本文

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.