锁定特定对象的 Java 线程
我有一个 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 的整数的线程进入,以及所有其他具有 value 的线程!= 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.
}
}
此代码使用对象本身作为锁.但它必须是 same 对象(即 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())
才能正常工作.此外,由于 java 的 int)/guide/language/autoboxing.html" rel="nofollow noreferrer">自动装箱.
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.
如果您为您的对象覆盖 equals()
和 hashcode()
,那么您可以使用对象本身而不是 object.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.
相关文章