我有一个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。
SQLException
我也不能在提交之前进行检查,因为不仅要进行多项检查1,而且要花费大量时间。
1
我对锁和线程的经验是有限的,但是我的想法基本上是将代码锁定在它所接收的对象上。我不知道是否例如说我收到一个Integer对象,并且将我的Integer锁定为值1,那是否只能阻止具有另一个Integer的值为1的线程进入,而所有其他具有该值的线程value != 1可以自由进入呢?这是怎么运作的?
value != 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()的数据类型可以是任何类型,包括原始类型(例如)。
ConcurrentHashMap.putIfAbsent()
objectInThreadA.getId().equals(objectInThreadB.getId())
int
如果为对象覆盖equals()和hashcode(),则可以使用对象本身代替object.getId(),这将是一种改进(感谢@TheCapn指出这一点)
equals()
hashcode()
object.getId()
此解决方案只能在一个JVM中使用。如果您的服务器是群集的,那么完全不同的球类运动和Java的锁定机制将无济于事。您将不得不使用集群锁定解决方案,这超出了此答案的范围。