在 Scala/Java 中使用 AtomicBoolean 进行数据库锁定是否安全?

Is it safe to use AtomicBoolean for database locking in Scala/Java?

我有一个应用程序,我想确保一个方法最多同时被调用一次,比如在更新数据库中的用户余额时。

我正在考虑使用以下锁定机制:(下面显示了 Scala 代码,但应该与 Java Lambdas 类似):

object Foo{
    val dbLocked = new java.util.concurrent.atomic.AtomicBoolean(false)

    def usingAtoimcDB[T](f: => T):T = {
        if (dbLocked.get) throw new Exception("db is locked")
        dbLocked.set(true)
        try f
        finally dbLocked.set(false)    
    }
}

usingAtoimcDB 可能被同时调用时使用这个安全吗?

编辑:下面更正的代码,如 中所指出:

def usingAtoimcDB[T](f: => T):T = {
  if(dbLocked.compareAndSet(false, true)) {
   //db is now locked
   try f
   finally dbLocked.set(false)
  } else {
   //db is already locked
   throw new Exception("db is locked")
  }
}

编辑 2:

使用自旋循环。这个也可以吗?

def usingAtoimcDB[T](f: => T):T = {
  while (!dbLocked.compareAndSet(false, true)) {Thread.sleep(1)}
  try f
  finally dbLocked.set(false)
} 

EDIT3:根据下面的答案和评论,我也在考虑使用队列。

是的,它应该按预期工作。我会使用 compareAndSet 调用稍微修改您的函数。

compareAndSet 方法的优点是它是一个原子操作 - 没有竞争条件,值将自动更改。

def usingAtoimcDB[T](f: => T):T = {
  if(dbLocked.compareAndSet(false, true)) {
   //db is now locked
   try f
   finally dbLocked.set(false)
  } else {
   //db is already locked
   throw new Exception("db is locked")
  }
}

您在上面发布的代码不是线程安全的,因为您没有使用原子检查和设置操作。两个线程可以同时执行 if (dbLocked.get) 语句并且都得到 false 作为答案,然后都将执行 dbLocked.set(true) 并调用 f.

如果你真的想使用 AtomicBoolean,那么你必须使用 compareAndSet 正如@leshkin 已经展示的那样 - 这是一个原子操作,可以一次完成检查和设置,不可能另一个线程同时做同样的事情,所以它是线程安全的。

您在这里使用 AtomicBoolean 作为锁。标准 Java 库中有 classes 更适合(并且专门制作)用于此目的;看看包裹 java.util.concurrent.locks.

例如,您可以使用 class ReentrantReadWriteLock,它结合了读取和写入的两个锁。写锁是独占的(当它被锁定时,其他人不能读或写);读锁是共享的(当它被锁定时,没有人可以写,但其他人可以同时读)。这允许并发有多个读取器,但一次只能有一个写入器,可能会提高效率(没有必要使读取成为独占操作)。

示例:

import java.util.concurrent.locks._

object Foo {
  private val lock: ReadWriteLock = new ReentrantReadWriteLock

  def doWriteOperation[T](f: => T): T = {
    // Locks the write lock
    lock.writeLock.lock()
    try {
      f
    } finally {
      lock.writeLock.unlock()
    }
  }

  def doReadOperation[T](f: => T): T = {
    // Locks the read lock
    lock.readLock.lock()
    try {
      f
    } finally {
      lock.readLock.unlock()
    }
  }
}

不可取。 您在同一服务器上的同一应用程序实例中请求同一段代码 运行是进行该交易的单点。也没有规定让这段代码脱颖而出。当你退休时,有人可能会启动第二个应用程序实例或其他任何东西。

而数据库 commit/rollback 是一种非常简单可靠的机制。

当你无法编写集成(单元)测试来确保这一点时,那就不要做。

如果你这样做:

  • 撤销普通数据库用户对 table 修改的权利
  • 添加已授予足够权限的新数据库用户

仍然:不要这样做。