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

时间:2016-08-19 07:21:12

标签: java multithreading scala java.util.concurrent

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

我正在考虑使用以下锁定机制:(显示下面的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可以安全使用吗?

编辑:下面更正的代码,如this answer所示:

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:

使用spinloop。这也可以吗?

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

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

3 个答案:

答案 0 :(得分:6)

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

数据库提交/回滚是一种非常简单和可靠的机制。

当您无法编写集成(单元)测试以确保这一点时,请不要这样做。

如果你这样做:

  • 撤销对普通数据库用户的表修改的权限
  • 添加具有足够权限的新数据库用户

仍然:不要这样做。

答案 1 :(得分:3)

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

如果你真的想使用AtomicBoolean,那么你必须使用compareAndSet,因为@leshkin已经显示了 - 这是一个原子操作,可以一次性进行检查和设置,而不需要另一个线程同时做同样的事情,这样它就是线程安全的。

您在这里使用AtomicBoolean作为锁定。标准Java库中有一些类更适合(并且特别制作)用于此目的;查看包java.util.concurrent.locks

例如,您可以使用类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()
    }
  }
}

答案 2 :(得分:2)

是的,它应该如预期的那样工作。我会使用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")
  }
}