锁定特定对象的Java线程 [英] Java threads locking on a specific object
问题描述
我有一个Web应用程序,我使用的是Oracle数据库,我的方法基本上是这样的:
I have a web application and I am using Oracle database and I have a method basically like this:
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个线程进入这个方法时都检查,当然还没有任何东西,然后它们都可以提交事务,创建一个重复的对象。
Right now there is no synchronization of any kind so n threads can of course access this method freely, the problem arises when 2 threads enter this method both check and of course there is nothing just yet, and then they can both commit the transaction, creating a duplicate object.
我不想在我的数据库中使用唯一的密钥标识符来解决这个问题,因为我认为我不应该捕获 SQLException
。
I do not want to solve this with a unique key identifier in my Database, because I don't think I should be catching that SQLException
.
我也无法在提交之前检查,因为有几个检查不仅 1
,这将花费相当长的时间。
I also cannot check right before the commit, because there are several checks not only 1
, which would take a considerable amount of time.
我对锁和线程的体验有限,但我的想法基本上是将这个代码锁定在它接收的对象上。我不知道例如说我收到一个整数对象,并且我用值1锁定我的整数,这只会阻止具有值为1的另一个Integer的线程进入,而所有其他线程都阻止值!= 1
可以自由进入吗?这是它的工作原理吗?。
My experience with locks and threads is limited, but my idea is basically to lock this code on the object that it is receiving. I don't know if for example say I receive an Integer Object, and I lock on my Integer with value 1, would that only prevent threads with another Integer with value 1 from entering, and all the other threads with value != 1
can enter freely?, is this how it works?.
如果这是它的工作方式,那么它是怎么回事?锁对象比较?它是如何确定它们实际上是同一个对象的?关于这一点的好文章也将受到赞赏。
Also if this is how it works, how is the lock object compared? how is it determined that they are in fact the same object?. A good article on this would also be appreciated.
你会如何解决这个问题?
How would you solve this?.
推荐答案
你的想法是一个好的。这是简单/天真的版本,但它不太可行:
Your idea is a good one. This is the simplistic/naive version, but it's unlikely to work:
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,那么你需要同步整个方法:
This code uses the object itself as the lock. But it has to be the same object (ie objectInThreadA == objectInThreadB) if it's to work. If two threads are operating on an object that is a copy of each other - ie has the same "id" for example, then you'll need to either synchronize the whole method:
public static synchronized void saveSomethingImportantToDataBase(Object theObjectIwantToSave) ...
这当然会大大降低并发性(使用该方法一次将吞吐量降至一个线程 - 要避免)。
which will of course greatly reduce concurrency (throughput will drop to one thread at a time using the method - to be avoided).
或者找到一种基于保存对象获取相同锁定对象的方法,如下所示:
Or find a way to get the same lock object based on the save object, like this approach:
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()的数据类型可以是任何东西,包括原语(例如 int
),因为java的 autoboxing 。
This last version it the recommended one: It will ensure that two save objects that share the same "id" are locked with the same lock object - the method ConcurrentHashMap.putIfAbsent()
is threadsafe, so "this will work", and it requires only that objectInThreadA.getId().equals(objectInThreadB.getId())
to work properly. Also, the datatype of getId() can be anything, including primitives (eg int
) due to java's autoboxing.
如果覆盖<$ c $对于您的对象,c> equals()和 hashcode()
,然后您可以使用对象本身而不是对象。 getId()
,这将是一个改进(感谢@TheCapn指出这一点)
If you override equals()
and hashcode()
for your object, then you could use the object itself instead of object.getId()
, and that would be an improvement (Thanks @TheCapn for pointing this out)
此解决方案仅适用于一个JVM 。如果您的服务器是集群的,那么整个不同的球类游戏和java的锁定机制将无法帮助您。您将不得不使用群集锁定解决方案,这超出了本答案的范围。
This solution will only work with in one JVM. If your servers are clustered, that a whole different ball game and java's locking mechanism will not help you. You'll have to use a clustered locking solution, which is beyond the scope of this answer.
这篇关于锁定特定对象的Java线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!