多线程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对象,并且我锁定了值为1的Integer,这是否只会阻止另一个值为1的Integer线程进入,而所有其他具有value != 1
的线程都可以自由进入?,这就是它的工作原理吗
另外,如果它是这样工作的,那么如何比较锁对象?如何确定它们实际上是同一个物体?。一篇关于这方面的好文章也将不胜感激
你将如何解决这个问题
# 1 楼答案
synchronized关键字会锁定所需的对象,以便其他方法无法访问它
# 2 楼答案
你的想法很好。这是一个过于简单/幼稚的版本,但不太可能奏效:
这段代码使用对象本身作为锁。但如果要工作,它必须是相同的对象(即objectInThreadA==objectInThreadB)。如果两个线程在一个对象上运行,而该对象是彼此的副本(例如,ie具有相同的“id”),则需要同步整个方法:
这当然会大大降低并发性(使用该方法时,吞吐量将一次降至一个线程,这是需要避免的)
或者找到一种基于save对象获取相同的锁对象的方法,如下所示:
最后一个版本是推荐的版本:它将确保共享相同“id”的两个保存对象被同一个锁对象锁定——方法
ConcurrentHashMap.putIfAbsent()
是线程安全的,因此“这将起作用”,只需要objectInThreadA.getId().equals(objectInThreadB.getId())
就可以正常工作。此外,getId()的数据类型可以是任何类型,包括原语(例如int
),因为java的autoboxing如果为对象重写
equals()
和hashcode()
,那么可以使用对象本身而不是object.getId()
,这将是一种改进(感谢@TheCapn指出这一点)此解决方案只能在一个JVM中使用。如果您的服务器是集群式的,那么一个完全不同的球类游戏和java的锁定机制对您没有帮助。您必须使用集群锁定解决方案,这超出了本答案的范围
# 3 楼答案
如果你能忍受偶尔的过度同步(即不需要时按顺序完成的工作),尝试以下方法:
IdLock类:
}
以及它的用途:
# 4 楼答案
以下是根据And360对Bohemian答案的评论改编的一个选项,它试图避免种族条件等。尽管我更喜欢我的other answer而不是这个问题,但有点:
可以将这些方法拆分为助手方法“get lock object”和“release lock”或其他方法来清理代码。这种方式比我的other answer感觉有点笨拙
# 5 楼答案
我的观点是,你并没有遇到真正的线程问题
最好让DBMS自动分配一个不冲突的行id
如果需要使用现有的行ID,请将它们存储为线程局部变量。 如果不需要共享数据,不要在线程之间共享数据
http://download.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html
当应用程序服务器或web容器运行时,Oracle dbms在保持数据一致性方面要好得多
“许多数据库系统在插入一行时自动生成一个唯一的密钥字段。Oracle数据库在序列和触发器的帮助下提供了相同的功能。JDBC 3.0引入了自动生成密钥的检索功能,使您能够检索这些生成的值。在JDBC 3.0中,以下接口被增强为支持检索自动生成的密钥功能。。。。"
http://download.oracle.com/docs/cd/B19306_01/java.102/b14355/jdbcvers.htm#CHDEGDHJ
# 6 楼答案
波希米亚人的答案似乎有种族条件的问题,如果一个线程在同步部分,而另一个线程从地图上删除同步对象,等等。所以这里有一个替代方案,利用WeakRef的
以及如何使用上述样式系统的更具体示例:
然后像这样在其他地方使用:
这样做的原因基本上是,如果两个具有匹配键的对象进入关键块,第二个对象将检索第一个已使用的锁(或留下但尚未使用的锁)。但是,如果未使用该方法,则两者都将保留该方法并删除对锁对象的引用,因此可以安全地收集该方法
如果您想要使用的同步点的“已知大小”有限(最终不必减小大小),那么您可能可以避免使用HashMap,而是使用ConcurrentHashMap,其putIfAbsent方法可能更容易理解