小编典典

锁定特定对象的Java线程

java

我有一个Web应用程序,并且正在使用Oracle数据库,并且有一种基本上像这样的方法:

public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) {
      if (!methodThatChecksThatObjectAlreadyExists) {
         storemyObject() //pseudo code
     }
     // Have to do a lot other saving stuff, because it either saves everything or nothing
     commit() // pseudo code to actually commit all my changes to the database.
}

现在没有任何类型的同步,因此n个线程当然可以自由地访问此方法,当2个线程都进入此方法都进行检查并且当然还没有任何东西时,就会出现问题,然后它们都可以提交事务并创建一个重复的对象。

我不想在数据库中使用唯一的密钥标识符来解决此问题,因为我认为我不应该抓住它SQLException

我也不能在提交之前进行检查,因为不仅要进行多项检查1,而且要花费大量时间。

我对锁和线程的经验是有限的,但是我的想法基本上是将代码锁定在它所接收的对象上。我不知道是否例如说我收到一个Integer对象,并且将我的Integer锁定为值1,那是否只能阻止具有另一个Integer的值为1的线程进入,而所有其他具有该值的线程value != 1可以自由进入呢?这是怎么运作的?

同样,如果这是如何工作的,那么如何比较锁对象?如何确定它们实际上是同一对象?对此的好文章也将不胜感激。

您将如何解决?


阅读 215

收藏
2020-10-16

共1个答案

小编典典

你的想法很好。这是简单/天真的版本,但是不太可能起作用:

public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) {
    synchronized (theObjectIwantToSave) {
        if (!methodThatChecksThatObjectAlreadyExists) {
            storemyObject() //pseudo code
        }
        // Have to do a lot other saving stuff, because it either saves everything or nothing
        commit() // pseudo code to actually commit all my changes to the database.
    }
}

此代码将对象本身用作锁。但是它必须是 相同的 对象(即objectInThreadA ==
objectInThreadB)才能起作用。如果两个线程正在一个互为 副本 的对象上运行-例如,具有相同的“ id”,则您将需要同步整个方法:

    public static synchronized void saveSomethingImportantToDataBase(Object theObjectIwantToSave) ...

当然,这将大大减少并发性(使用该方法,吞吐量将一次下降至一个线程-避免使用)。

或者找到一种基于保存对象获取 相同 锁定对象的方法,例如这种方法:

private static final ConcurrentHashMap<Object, Object> LOCKS = new ConcurrentHashMap<Object, Object>();
public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) {
    synchronized (LOCKS.putIfAbsent(theObjectIwantToSave.getId(), new Object())) {
        ....    
    }
    LOCKS.remove(theObjectIwantToSave.getId()); // Clean up lock object to stop memory leak
}

最后一个版本是推荐的版本:它将确保使用相同的锁定对象锁定共享相同“ id”的两个保存对象-
该方法ConcurrentHashMap.putIfAbsent()是线程安全的,因此“此方法将起作用”,并且仅要求该对象objectInThreadA.getId().equals(objectInThreadB.getId())才能正常工作。同样,int由于Java的autoboxing,getId()的数据类型可以是任何类型,包括原始类型(例如)。

如果为对象覆盖equals()hashcode(),则可以使用对象本身代替object.getId(),这将是一种改进(感谢@TheCapn指出这一点)

此解决方案只能在一个JVM中使用。如果您的服务器是群集的,那么完全不同的球类运动和Java的锁定机制将无济于事。您将不得不使用集群锁定解决方案,这超出了此答案的范围。

2020-10-16