Java选择性同步

发布于 2024-12-11 16:19:39 字数 469 浏览 0 评论 0原文

我正在维护一个非常旧的应用程序,最近我遇到了一个“多线程”错误。 这里,在一个方法中,要将一个值插入到数据库中,首先检查该记录是否存在,然后如果不存在,则将其插入到数据库中。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

一人独醉 2024-12-18 16:19:39

我想说,更好的方法是让数据库为您做这件事(如果可能的话)。假设您想要更新或插入的数据库上的行具有唯一约束,那么我通常的方法是

  • 在发生 SQLException 时无条件插入该行
  • ,检查是否是由于插入时的重复键所致错误,如果是,则进行更新,否则重新抛出 SQLException。

如果您可以将这些语句包装在数据库事务中,那么您就不必担心两个线程互相干扰。

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

  • unconditionally insert the row
  • if an SQLException occurs, check to see if it is due to a duplicate key on insert error, if it is, do the update, otherwise rethrow the SQLException.

If you can wrap those statements in a database transaction, then you don't have to worry about two threads trampling on each other.

罪歌 2024-12-18 16:19:39

如果逻辑确实是“如果尚不存在则创建它”,那么最好将逻辑下推到数据库中。例如,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.

烦人精 2024-12-18 16:19:39

仅当该对象实例是唯一在表中插入内容的实例时,这种方法才有效。如果不是,那么两个线程将在两个不同的对象上同步,并且同步将不起作用。简而言之:该对象应该是单例,并且任何其他对象都不应插入到该表中。

即使有一个唯一的对象实例插入,如果您有任何其他应用程序或任何其他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.

今天小雨转甜 2024-12-18 16:19:39

由于您只想禁止此方法使用相同的参数运行,因此您可以使用 ConcurrentMap 代替,然后调用 putIfAbsent 并在继续之前检查其返回值。这将允许您针对不同的参数同时运行该方法。

Since you only want to forbid this method from running with the same params, you can use a ConcurrentMap instead and then call putIfAbsent and check its return value before proceeding. This will allow you to run the method concurrently for different arguments.

[旋木] 2024-12-18 16:19:39

对我来说看起来不错。您可以使用一些java.util.concurrent 辅助工具,例如ReentrantLock

最好利用某种乐观事务:尝试插入并捕获异常。如果记录刚刚插入,则无需执行任何操作。

Looks fine to me. You can use some of the java.util.concurrent aids, like a ReentrantLock.

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.

此岸叶落 2024-12-18 16:19:39

一言以蔽之: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.

一绘本一梦想 2024-12-18 16:19:39

您可以使整个方法同步。我倾向于发现“此方法一次只能由一个线程运行”的一个很好的标记。但这是我个人的偏好。

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.

您的好友蓝忘机已上羡 2024-12-18 16:19:39

太粗粒度锁定的缺点是性能下降。如果该方法被频繁调用,就会成为性能瓶颈。这里还有另外两种方法:

  • 如果可能,将并发代码移至数据库语句中。
  • 使用非阻塞数据结构(例如 ConcurrentMap)并维护已知条目列表(必须在启动时预热)。这允许您以最少的锁定运行两个方法,并且无需同步代码。原子 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:

  • Move your concurrent code into your database statement, if possible.
  • Use a non-blocking data structure such as 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 atomic putIfAbsent() can be used to check if it must be added or not.
冰火雁神 2024-12-18 16:19:39

正如其他人所说,您当前的方法很好。尽管根据您的要求,还需要考虑其他事项,

  • 但这是应用程序中将这些记录插入数据库的唯一位置吗?如果不是,那么即使同步,插入仍然可能失败
  • 操作失败的频率是多少?如果操作失败的次数与运行该方法的次数相比,通过捕获适当的异常来检测失败可能是有益的。由于同步线程所涉及的开销,这可能是有益的。
  • 当您的应用程序检测到此类故障时需要做什么?

As others have stated your current approach is fine. Although depending on your requirements there are other things to consider

  • Is this the only place in your application where you insert these records into the db? If no then the insert could still fail even with synchronisation
  • How often does theoperation fail? If the number of times the operations fail compared to the number of times you run the method it may be beneficial to detect the failure by catching an appropriate exception. This may be beneficial due to the overhead involved in synchronising threads.
  • What does your application need to do when it detects this kind of failure?
节枝 2024-12-18 16:19:39

乍一看,您的解决方案似乎没问题,但如果您想更改它,这里有两个选项:

  • 使用数据库事务
  • 使用 java.util.concurrent.locks 中的锁
   

    Lock lock = new ReentrantLock();
    .....

    createSomething(params)
    {
      ....
      ....
      try {
        lock.lock();
        if( !isPresentInDb(params) )
        {
          .....
          .....
          .....
          insertIntoDb(params)
        }
      finally {
        lock.unlock;
      }
    }

On first sight your solution seems ok, but if you want to change it here are two options:

  • use db transactions
  • use locks from java.util.concurrent.locks
   

    Lock lock = new ReentrantLock();
    .....

    createSomething(params)
    {
      ....
      ....
      try {
        lock.lock();
        if( !isPresentInDb(params) )
        {
          .....
          .....
          .....
          insertIntoDb(params)
        }
      finally {
        lock.unlock;
      }
    }

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文