2016-08-19 104 views
2

我有一个应用程序,我想确保一个方法最多同时调用一次,比如在更新数据库中的用户余额时。在Scala/Java中使用AtomicBoolean进行数据库锁定安全吗?

我想到的是使用下面的锁体:(显示Scala代码的下方,而应该是与Java Lambda表达式类似):

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) 
} 

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

+0

在Java中使用它是否安全?如果是,那么它将从Scala安全。 –

+0

@SarveshKumarSingh添加了Java标记,所以Java人可以回答这个问题。 – Jus12

+3

您上面发布的代码不是线程安全的;两个线程可以通过'if(dbLocked.get)'调用并设置原子布尔值。您必须使用'compareAndSet'方法使其成为原子。像'java.util.concurrent.locks.ReentrantReadWriteLock'这样的类比AtomicBoolean更合适。 – Jesper

回答

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") 
    } 
} 
+1

感谢您指出。使用'compareAndSet'更好。 – Jus12

+0

EDIT2中的代码如何? – Jus12

+0

@ Jus12 hm,虽然代码看起来对我来说是正确的,但我不喜欢睡眠线程。您只需等待/旋转即可浪费CPU时间。我建议在检查后立即返回(有异常或一些错误代码),然后睡觉。为了使它更有效率,你可以等待增长的时间间隔,如2ms,4ms,16ms ... – leshkin

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() 
    } 
    } 
} 
+0

谢谢,这看起来不错,并允许细粒度锁定。在我的情况下,'f'包含多个在代码中交错的读写操作,所以我需要一些重构。 'AtomicBoolean'或全局'写锁'似乎是最简单的解决方案。 – Jus12

+0

你能评论我的EDIT2吗? – Jus12

+0

@ Jus12为什么要使用spinloop;为什么在JDK已经有一整套经过充分测试和优化的锁类时尝试自己实现呢?只需使用标准类而不是自己实现它。 – Jesper

6

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

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

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

如果你做到这一点:

  • 撤销权的表修改为普通的数据库用户
  • 添加一个新的数据库使用谁有足够的权利授予

而且还是:不做到这一点。

+0

注意点。如果处理分布式数据库,它不会是一个复杂的提交/回滚网络吗? – Jus12

+0

分布式数据库系统有其自己的机制,如主数据库。在软件方面,可能会提供特殊的访问权限,例如只使用主数据库等。我承认,**一个易失性的原子锁可能仍然有用**,但不是保证,而是一个排队优化。 –

+0

目前的设计意图是只使用它作为db的入口点。此外,这可能不会用于生产,而只是原型。 – Jus12