锁定特定对象的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,这将花费相当多的时间.

我对锁和线程的体验是有限的,但我的想法基本上是将这个代码锁定在它接收的对象上.我不知道例如说我收到一个整数对象,并且我用值1锁定我的整数,这只会阻止具有值为1的另一个Integer的线程进入,而所有其他具有值!= 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())才能正常工作.此外,getId()的数据类型可以是任何东西,包括由于java的autoboxing引起的基元(例如int).

如果为对象重写equals()和hashcode(),那么你可以使用对象本身而不是object.getId(),这将是一个改进(感谢@TheCapn指出这一点)

此解决方案仅适用于一个JVM.如果您的服务器是集群的,那么整个不同的球类游戏和java的锁定机制将无法帮助您.您将不得不使用群集锁定解决方案,这超出了本答案的范围.

转载注明原文:锁定特定对象的Java线程 - 代码日志