在 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 修改的权利
- 添加已授予足够权限的新数据库用户
仍然:不要这样做。
我有一个应用程序,我想确保一个方法最多同时被调用一次,比如在更新数据库中的用户余额时。
我正在考虑使用以下锁定机制:(下面显示了 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 修改的权利
- 添加已授予足够权限的新数据库用户
仍然:不要这样做。