同步静态方法如何在Java中工作?

时间:2009-02-23 19:07:17

标签: java multithreading hibernate synchronization

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

8 个答案:

答案 0 :(得分:221)

更广泛地解决这个问题......

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

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

答案 1 :(得分:134)

通过在静态方法锁上使用synchronized,您将synchronize the class methods and attributes(而不是实例方法和属性)

所以你的假设是正确的。

  

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

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

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

想象一下以下场景:

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

使用你的方法你唯一得到的就是确保一个接一个地被调用,当这在DB中发生时,因为RDBMS将阻止它们从A中插入一半信息而在B中插入一半信息。同一时间。结果将相同,但只会慢5倍(或更多)。

看看Hibernate文档中的"Transactions and Concurrency"章节可能会更好。大多数时候,你试图解决的问题已经解决,并且已经有了更好的解决方法。

答案 2 :(得分:17)

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

答案 3 :(得分:14)

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

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

答案 4 :(得分:9)

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

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

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

答案 7 :(得分:2)

synchronized Java关键字如何工作

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

对于您而言,每个方法调用都会:

  • 创建新的SessionFactory
  • 创建新的Session
  • 获取实体
  • 将实体返回给呼叫者

但是,这些是您的要求:

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

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

SessionFactory最佳做法

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

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

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

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

Session应该始终关闭

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

如果在数据库中找不到该实体,则根据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实体并提交了交易
  • 在抛出Exception的情况下,我回滚了交易

有关ACID和数据库事务的更多详细信息,请同时查看this article