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

在Scala/Java中使用AtomicBoolean进行数据库锁定安全吗?,java,multithreading,scala,java.util.concurrent,Java,Multithreading,Scala,Java.util.concurrent,我有一个应用程序,我想确保一个方法最多并发调用一次,比如在更新数据库中的用户平衡时 我正在考虑使用以下锁定机制:(下面显示Scala代码,但应该与Java Lambdas类似): 当可以同时调用usingAtoimcDB时,使用此选项安全吗 编辑:下面的更正代码,如中所述: 编辑2: 使用旋转环。这也行吗 def usingAtoimcDB[T](f: => T):T = { while (!dbLocked.compareAndSet(false, true)) {Thread.sl

我有一个应用程序,我想确保一个方法最多并发调用一次,比如在更新数据库中的用户平衡时

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

当可以同时调用
usingAtoimcDB
时,使用此选项安全吗

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

编辑2:

使用旋转环。这也行吗

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

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

是的,它应该可以正常工作。我将使用
compareAndSet
call稍微修改您的函数

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(dblock.get)
语句,并都得到
false
作为答案,然后都执行
dblock.set(true)
并调用
f

如果您真的想使用
AtomicBoolean
,那么您必须使用@leshkin已经展示过的
compareAndSet
——这是一个原子操作,它一次完成检查和设置,而另一个线程不可能同时执行相同的操作,因此它是线程安全的

这里使用的是一个
AtomicBoolean
作为锁。标准Java库中有一些类更适合(并专门制作)此用途;看看这个包裹

例如,您可以使用类
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()
    }
  }
}
不可取。您请求在同一服务器上的同一应用程序实例中运行的同一批代码是执行该事务的单点。也没有让这一准则脱颖而出的规定。当您退休时,可能会有人启动第二个应用程序实例或其他什么

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

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

如果您这样做:

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


还有:不要这样做。

在Java中这样使用它安全吗?如果是,那么Scala是安全的。@SarveshKumarSingh添加了Java标记,所以Java人员可以回答这个问题。您上面发布的代码不是线程安全的;两个线程可以通过
if(dblock.get)
调用,并都设置原子布尔值。必须使用
compareAndSet
方法使其原子化。像
java.util.concurrent.locks.ReentrantReadWriteLock
这样的类在这里比
AtomicBoolean
@Jesper更合适,像leshkin在回答中指出的那样使用
compareAndSet
怎么样,您必须使用
compareAndSet
使其原子化,否则它不是线程安全的。谢谢,这看起来不错,允许细粒度锁定。在我的例子中,
f
包含代码中交错的多个读写操作,因此我需要一些重构
AtomicBoolean
或global
write lock
似乎是最简单的解决方案。你能评论一下我的EDIT2吗?@Jus12为什么要使用spinloop;既然JDK已经有了一整套经过良好测试和优化的锁类,为什么还要自己实现呢?只要使用标准类,而不是自己实现。如果我没有弄错的话,使用读/写锁需要编辑
f
(由其他人编写)的代码。如果是这样,那么最简单的解决方案就是使用某种包装器,而不了解
f
@Jus12中操作的语义。如果您只需要一个排他锁,请使用
ReentrantLock
而不是
reentranderwritelock
。参见包装的API文件。注意事项。如果处理分布式数据库,这不是一个复杂的提交/回滚网络吗?分布式数据库系统有自己的机制,就像主数据库一样。在软件方面,可能会提供一种特殊的访问,比如只使用masterdb之类的。我承认,易失性原子锁可能仍然有用,但不是作为保证,而是作为一种排队优化。当前的设计意图是仅将其用作db的入口点。此外,这可能不会用于生产,而只用于原型。那么请原谅我生硬的学究作风。您也可以考虑在DB目录中锁定文件,或者如果时间没有问题的话。祝你好运,别担心。听到任何方法的利弊总是很好的+1来自我。@Jus12-hm,虽然代码在我看来是正确的,但我不喜欢休眠线程。你只是把你的CPU时间浪费在等待/旋转上。我建议检查后立即返回(有异常或一些错误代码),然后再睡觉。为了提高效率,你可以按指数增长的间隔等待,比如2毫秒、4毫秒、16毫秒……我可以取消睡眠,然后做一个while循环。我仍在测试在我的环境中哪个更快。
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")
  }
}
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()
    }
  }
}