Java选择性同步



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

createSomething(params)
{
  ....
  ....
  if( !isPresentInDb(params) )
  {
    .....
    .....
    .....
    insertIntoDb(params)
   }
 ...
 }

当多个线程调用此方法时,两个或多个具有相同参数的线程可能会通过isPresentInDb检查,一个线程插入成功,其他线程失败。

为了解决这个问题,我将两个数据库交互都包含在一个单独的synchronized(this)块中。但是有没有更好的办法呢?

编辑:它更像是选择性同步,只有具有相同参数的线程需要被同步。是否可以选择同步?

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

  • 无条件插入行
  • 如果SQLException发生,检查它是否是由于插入错误的重复键,如果是,做更新,否则重新抛出SQLException。

如果你可以把这些语句包装在一个数据库事务中,那么你就不必担心两个线程会互相践踏。

如果逻辑真的是"如果它不存在就创建这个",那么将逻辑下推到数据库中可能更好。例如,MySQL有"INSERT IGNORE"语法,如果它违反主键约束,则会导致它忽略插入。这对于您的代码可能不可行,但值得考虑。

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

即使有一个唯一的对象实例插入,如果你有任何其他应用程序,或者任何其他JVM,在这个表中插入,那么同步不会给你带来任何保证。

这样做总比什么都不做好,但不能保证插入总是成功。如果没有,那么事务将由于(希望如此)违反约束而回滚。如果没有任何惟一约束来保证数据库中的惟一性,并且有多个应用程序并行插入,那么就无法做任何事情来避免重复。

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

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

最好使用某种乐观事务:尝试插入,并捕获异常。如果记录刚刚插入,则什么都不做。

一句话NO。没有比这更好的办法了。由于要使检查-更新类型的操作原子化,您必须将逻辑放在同步块中。

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

过于粗粒度的锁的缺点是性能下降。如果经常调用该方法,它将成为性能瓶颈。下面是另外两种方法:

    如果可能的话,将并发代码移到数据库语句中。
  • 使用非阻塞数据结构,如ConcurrentMap,并维护一个已知条目列表(必须在启动时预热)。这允许您以最小的锁定运行方法,而无需同步代码。原子putIfAbsent()可以用来检查是否必须添加。

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

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

乍一看,你的解决方案似乎还可以,但如果你想改变它,这里有两个选择:

  • use db transactions
  • java.util.concurrent.locks
   
    Lock lock = new ReentrantLock();
    .....
    createSomething(params)
    {
      ....
      ....
      try {
        lock.lock();
        if( !isPresentInDb(params) )
        {
          .....
          .....
          .....
          insertIntoDb(params)
        }
      finally {
        lock.unlock;
      }
    }

最新更新