我有一个应用程序,我想确保一个方法最多同时调用一次,比如在数据库中更新用户余额时。
我正在考虑使用以下锁定机制:(显示下面的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:根据下面的答案和评论,我也在考虑使用队列。
答案 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")
}
}