Java选择性同步
我正在维护一个非常旧的应用程序,最近我遇到了一个“多线程”错误。 这里,在一个方法中,要将一个值插入到数据库中,首先检查该记录是否存在,然后如果不存在,则将其插入到数据库中。
createSomething(params)
{
....
....
if( !isPresentInDb(params) )
{
.....
.....
.....
insertIntoDb(params)
}
...
}
这里,当多个线程调用此方法时,两个或多个具有相同参数的线程可能会通过 isPresentInDb 检查,一个线程插入成功,其他线程插入失败。
为了解决这个问题,我将两个数据库交互封装到一个 synchronized(this)
块中。但有更好的方法吗?
编辑:它更像是选择性同步,只有具有相同参数的线程才需要同步。是否可以选择性同步?
I'm maintaining a very old application, and recently I came across a 'multi thread' bug.
Here, in a method, to insert a value into a db, the record is first checked that it exists or not, then if it does not exist, it is inserted into the db.
createSomething(params)
{
....
....
if( !isPresentInDb(params) )
{
.....
.....
.....
insertIntoDb(params)
}
...
}
Here when multiple threads invoke this method, two or more threads with same params may cross the isPresentInDb check, one thread inserts successfully, the other threads fail.
To solve this problem I enclosed both the db interactions into a single synchronized(this)
block. But is there a better way of doing this?
Edit: it is more like selective synchronization, only threads with same params need to by synchronized. Is selective synchronization possible?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
我想说,更好的方法是让数据库为您做这件事(如果可能的话)。假设您想要更新或插入的数据库上的行具有唯一约束,那么我通常的方法是
如果您可以将这些语句包装在数据库事务中,那么您就不必担心两个线程互相干扰。
I'd say the better way to do this would be to let the database do it for you if at all possible. Assuming the row on the database that you are wanting to either update or insert has a unique constraint on it, then my usual approach would be
If you can wrap those statements in a database transaction, then you don't have to worry about two threads trampling on each other.
如果逻辑确实是“如果尚不存在则创建它”,那么最好将逻辑下推到数据库中。例如,MySQL 有“INSERT IGNORE”语法,如果插入违反主键约束,它将导致它忽略插入。这对于您的代码来说可能是不可能的,但值得考虑。
If the logic is really "create this if it doesn't already exist", it could be better still to push the logic down into the database. For example, MySQL has "INSERT IGNORE" syntax that will cause it to ignore the insert if it would violate a primary key constraint. It may not be possible for your code, but worth considering.
仅当该对象实例是唯一在表中插入内容的实例时,这种方法才有效。如果不是,那么两个线程将在两个不同的对象上同步,并且同步将不起作用。简而言之:该对象应该是单例,并且任何其他对象都不应插入到该表中。
即使有一个唯一的对象实例插入,如果您有任何其他应用程序或任何其他JVM,在该表中插入,那么同步不会给您带来任何保证。
这样做总比什么都不做好,但并不能保证插入总是成功。如果没有,那么事务将由于(希望)违反约束而回滚。如果您没有任何唯一约束来保证数据库中的唯一性,并且您有多个应用程序并行插入,那么您无法采取任何措施来避免重复。
This way of doing would only work if this object instance is the only one which inserts something in the table. If it's not, then two threads will synchronize on two different objects, and the synchronization won't work. To make it short : the object should be a singleton, and no other object should insert into this table.
Even if there is a unique object instance inserting, if you have any other application, or any other JVM, inserting in this table, then the synchronization won't bring you any guarantee.
Doing this is better than nothing, but doesn't guarantee that the insert will always succeed. If it doesn't, then the transaction will rollback due (hopefully) to a constraint violation. If you don't have any unique constraint to guarantee the uniqueness in the database, and you have several applications inserting in parallel, then you can't do anything to avoid duplicates.
由于您只想禁止此方法使用相同的参数运行,因此您可以使用
ConcurrentMap
代替,然后调用putIfAbsent
并在继续之前检查其返回值。这将允许您针对不同的参数同时运行该方法。Since you only want to forbid this method from running with the same params, you can use a
ConcurrentMap
instead and then callputIfAbsent
and check its return value before proceeding. This will allow you to run the method concurrently for different arguments.对我来说看起来不错。您可以使用一些
java.util.concurrent
辅助工具,例如ReentrantLock
。最好利用某种乐观事务:尝试插入并捕获异常。如果记录刚刚插入,则无需执行任何操作。
Looks fine to me. You can use some of the
java.util.concurrent
aids, like aReentrantLock
.It will be better to utilize some sort of optimistic transactions: try to insert, and catch an exception. If the records has just been inserted, simply do nothing.
一言以蔽之:NO。没有比这更好的方法了。由于要使检查然后更新类型的操作原子化,您必须将逻辑放入同步块中。
In one word NO. there is not better way than this. Since to make check-then-update kind of operations atomic you must have to put the logic inside a synchronized block.
您可以使整个方法同步。我倾向于发现“此方法一次只能由一个线程运行”的一个很好的标记。但这是我个人的偏好。
You could make the whole method synchronized. I tend to find that a good marker for "this method only gets run by one thread at a time". That's my personal preference though.
太粗粒度锁定的缺点是性能下降。如果该方法被频繁调用,就会成为性能瓶颈。这里还有另外两种方法:
putIfAbsent()
可用于检查是否必须添加。The downside of too coarse-grained locking is performance degradation. If the method is called often, it will become a performance bottleneck. Here are two other approaches:
ConcurrentMap
and maintain a list of known entries (must be warmed up on startup). This allows you two run the method with minimal locking, and without synchronizing the code. An atomicputIfAbsent()
can be used to check if it must be added or not.正如其他人所说,您当前的方法很好。尽管根据您的要求,还需要考虑其他事项,
As others have stated your current approach is fine. Although depending on your requirements there are other things to consider
乍一看,您的解决方案似乎没问题,但如果您想更改它,这里有两个选项:
On first sight your solution seems ok, but if you want to change it here are two options: