ReentrantReadWriteLock 卡在解锁状态
ReentrantReadWriteLock gets stuck on unlock
我有一个 class 用于获取和释放文件的锁。我使用的 customKey class 只是一个带有 id 字符串(id 是文件)的 ReentrantReadWriteLock。出于某种原因,这仅在某些情况下有效,并且在大多数情况下它会挂起所有东西的解锁 - 我的调试器跟踪它一直在那里使用然后就卡住了。
我做错了什么?如果一个线程崩溃并且没有释放它的锁,我会得到,但是这里一个线程试图调用解锁并且没有进一步。
这是锁定的方法:
override fun acquire(lockId: String?, ownerId: String?, sequence: Long): Boolean
{
if (lockId != null)
{
lockedList.find { customLock -> customLock.Id == lockId }.apply {
if (this != null) //lock already exists for this ID
{
println("Locking file $lockId Existing lock")
this.writeLock().lock()
println("Locked file $lockId")
} else //lock does not exist
{
val newLock = CustomLock(lockId)
lockedList.add(newLock)
println("Locking file $lockId")
newLock.writeLock().lock()
println("Locked file $lockId")
}
}
return true
} else
{
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}
释放方法是这样的:
override fun release(lockId: String?, ownerId: String?)
{
if (lockId != null)
{
lockedList.find { customLock -> customLock.Id == lockId }.apply {
if (this != null)
{
println("Unlocking file $lockId")
this.writeLock().unlock()
if (this.isWriteLocked)
{
throw Exception("ERROR: Unlocking failed!")
}
} else
{
throw Exception("ERROR: Lock not found!")
}
}
}
}
体系结构就别讲了,这个是作业写的。另外请忽略 ownerId 和序列变量。
编辑:我尝试只使用一个锁,虽然效率不高,但它确实有效,所以@gidds 可能有问题,但 ConcurrentHashMap 和 ConcurrentLinkedQueue(替换 List 更简单)都没有解决问题.
EDIT2:这是我使用 ConcurrentHashMap 的新 class。它仍然无法正常工作,任何人都可以指出我搞砸的地方吗?谢谢
class LockServer(port: Int) : LockConnector, RemoteException()
{
private val lockedList = ConcurrentHashMap<String, CustomLock>()
private var registry: Registry = LocateRegistry.createRegistry(port)
init
{
registry.bind(ServiceNames.LockService.toString(), UnicastRemoteObject.exportObject(this, port))
}
/**
* Method acquire() should block the multiple calls from the clients for each specific lockId string.
* It means when one client acquires the lock "A" and the "A" is not locked by any other clients,
* the method should record the lock and return true. If the "A" is already locked by any other client,
* the method is blocked and continues only after the lock "A" is released.
* (Note: Return value false is not used in this basic implementation.
* Parameters ownerId and sequence are also not used in this basic implementation.)
*/
override fun acquire(lockId: String?, ownerId: String?, sequence: Long): Boolean
{
if (lockId != null)
{
lockedList.computeIfPresent(lockId){id, value ->
println("Locking file $id Existing lock")
value.writeLock().lock()
println("Locked file $id")
return@computeIfPresent value
}
lockedList.computeIfAbsent(lockId){
val newLock = CustomLock(it)
println("Locking file $lockId")
newLock.writeLock().lock()
println("Locked file $lockId")
return@computeIfAbsent newLock
}
return true
} else
{
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}
/**
* Method release() should release the lock and unblock all waiting acquire() calls for the same lock.
* (Note: Parameter ownerId is not used in this basic implementation.)
*/
override fun release(lockId: String?, ownerId: String?)
{
if (lockId != null)
{
lockedList.computeIfPresent(lockId){ id, value ->
println("Unlocking file $id")
value.writeLock().unlock()
println("Unlocked file $id")
return@computeIfPresent value
}
}
}
/**
* Method stop() unbinds the current server object from the RMI registry and unexports it.
*/
override fun stop()
{
registry.unbind(ServiceNames.LockService.toString())
}
}
编辑3:
acquire 的新实现:
lockedList.compute(lockId){id, value ->
if (value == null)
{
println("Locking file $id")
val newLock = CustomLock(id)
newLock.writeLock().lock()
println("Locked file $id")
return@compute newLock
}
println("Locking file $id Existing lock")
value.writeLock().lock()
println("Locked file $id")
return@compute value
}
并发布:
println("Unlocking $lockId")
lockedList[lockId]!!.writeLock().unlock()
println("Unlocked $lockId")
还是一样的失败
这可能不是您的问题,但代码在添加新锁时存在竞争条件:如果两个线程尝试锁定同一个(新)文件,则它们都可以为该文件创建一个锁。两把锁都会被添加到列表中,但之后只会找到第一把。 (这假定列表本身是 thread-safe;否则其中一个添加可能会失败、永远循环或使列表处于不一致状态并稍后崩溃。)
你可以通过一些同步来解决这个问题。但更好的方法可能是将锁存储在 ConcurrentHashMap (keyed on the lock ID) instead of a list, and use an atomic operation such as computeIfAbsent() 中以安全地创建新锁。 (这也将提高渐近性能,因为它可以避免每次扫描列表。)
此外,作为风格问题,在锁上使用 apply()
看起来有点别扭。 (它的通常用途是自定义 newly-created 对象。)我认为 let()
在那里会更惯用;您只需要将 this
更改为 it
即可。当然,也可以使用 old-fashioned 临时变量。
这可能不是 LockServer class 的问题,而是正在使用它的问题:
线程 1:
acquire("file1")
acquire("file2")
release("file2")
release("file1")
线程 2:
acquire("file2")
acquire("file1")
release("file1")
release("file2")
而恰好执行顺序如下:
thread1.acquire("file1")
thread2.acquire("file2")
thread1.acquire("file2") //locked by thread2, waiting
thread2.acquire("file1") //locked by thread1... BOOM, deadlock!
更新:
考虑对现有锁使用 tryLock()
(可能有一些超时)而不是简单的 lock()
:
fun tryAcquire(lockId: String?, timeout: Long, unit: TimeUnit): Boolean {
if (lockId != null) {
var success = false
lockedList.compute(lockId) { id, value ->
if (value == null) {
println("Locking file $id")
val newLock = CustomLock(id)
newLock.writeLock().lock()
success = true
println("Locked file $id")
return@compute newLock
}
println("Locking file $id Existing lock")
val lock = value.writeLock()
if (lock.tryLock() || lock.tryLock(timeout, unit)) {
success = true
println("Locked file $id")
}
return@compute value
}
return success
} else {
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}
我有一个 class 用于获取和释放文件的锁。我使用的 customKey class 只是一个带有 id 字符串(id 是文件)的 ReentrantReadWriteLock。出于某种原因,这仅在某些情况下有效,并且在大多数情况下它会挂起所有东西的解锁 - 我的调试器跟踪它一直在那里使用然后就卡住了。
我做错了什么?如果一个线程崩溃并且没有释放它的锁,我会得到,但是这里一个线程试图调用解锁并且没有进一步。
这是锁定的方法:
override fun acquire(lockId: String?, ownerId: String?, sequence: Long): Boolean
{
if (lockId != null)
{
lockedList.find { customLock -> customLock.Id == lockId }.apply {
if (this != null) //lock already exists for this ID
{
println("Locking file $lockId Existing lock")
this.writeLock().lock()
println("Locked file $lockId")
} else //lock does not exist
{
val newLock = CustomLock(lockId)
lockedList.add(newLock)
println("Locking file $lockId")
newLock.writeLock().lock()
println("Locked file $lockId")
}
}
return true
} else
{
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}
释放方法是这样的:
override fun release(lockId: String?, ownerId: String?)
{
if (lockId != null)
{
lockedList.find { customLock -> customLock.Id == lockId }.apply {
if (this != null)
{
println("Unlocking file $lockId")
this.writeLock().unlock()
if (this.isWriteLocked)
{
throw Exception("ERROR: Unlocking failed!")
}
} else
{
throw Exception("ERROR: Lock not found!")
}
}
}
}
体系结构就别讲了,这个是作业写的。另外请忽略 ownerId 和序列变量。
编辑:我尝试只使用一个锁,虽然效率不高,但它确实有效,所以@gidds 可能有问题,但 ConcurrentHashMap 和 ConcurrentLinkedQueue(替换 List 更简单)都没有解决问题.
EDIT2:这是我使用 ConcurrentHashMap 的新 class。它仍然无法正常工作,任何人都可以指出我搞砸的地方吗?谢谢
class LockServer(port: Int) : LockConnector, RemoteException()
{
private val lockedList = ConcurrentHashMap<String, CustomLock>()
private var registry: Registry = LocateRegistry.createRegistry(port)
init
{
registry.bind(ServiceNames.LockService.toString(), UnicastRemoteObject.exportObject(this, port))
}
/**
* Method acquire() should block the multiple calls from the clients for each specific lockId string.
* It means when one client acquires the lock "A" and the "A" is not locked by any other clients,
* the method should record the lock and return true. If the "A" is already locked by any other client,
* the method is blocked and continues only after the lock "A" is released.
* (Note: Return value false is not used in this basic implementation.
* Parameters ownerId and sequence are also not used in this basic implementation.)
*/
override fun acquire(lockId: String?, ownerId: String?, sequence: Long): Boolean
{
if (lockId != null)
{
lockedList.computeIfPresent(lockId){id, value ->
println("Locking file $id Existing lock")
value.writeLock().lock()
println("Locked file $id")
return@computeIfPresent value
}
lockedList.computeIfAbsent(lockId){
val newLock = CustomLock(it)
println("Locking file $lockId")
newLock.writeLock().lock()
println("Locked file $lockId")
return@computeIfAbsent newLock
}
return true
} else
{
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}
/**
* Method release() should release the lock and unblock all waiting acquire() calls for the same lock.
* (Note: Parameter ownerId is not used in this basic implementation.)
*/
override fun release(lockId: String?, ownerId: String?)
{
if (lockId != null)
{
lockedList.computeIfPresent(lockId){ id, value ->
println("Unlocking file $id")
value.writeLock().unlock()
println("Unlocked file $id")
return@computeIfPresent value
}
}
}
/**
* Method stop() unbinds the current server object from the RMI registry and unexports it.
*/
override fun stop()
{
registry.unbind(ServiceNames.LockService.toString())
}
}
编辑3: acquire 的新实现:
lockedList.compute(lockId){id, value ->
if (value == null)
{
println("Locking file $id")
val newLock = CustomLock(id)
newLock.writeLock().lock()
println("Locked file $id")
return@compute newLock
}
println("Locking file $id Existing lock")
value.writeLock().lock()
println("Locked file $id")
return@compute value
}
并发布:
println("Unlocking $lockId")
lockedList[lockId]!!.writeLock().unlock()
println("Unlocked $lockId")
还是一样的失败
这可能不是您的问题,但代码在添加新锁时存在竞争条件:如果两个线程尝试锁定同一个(新)文件,则它们都可以为该文件创建一个锁。两把锁都会被添加到列表中,但之后只会找到第一把。 (这假定列表本身是 thread-safe;否则其中一个添加可能会失败、永远循环或使列表处于不一致状态并稍后崩溃。)
你可以通过一些同步来解决这个问题。但更好的方法可能是将锁存储在 ConcurrentHashMap (keyed on the lock ID) instead of a list, and use an atomic operation such as computeIfAbsent() 中以安全地创建新锁。 (这也将提高渐近性能,因为它可以避免每次扫描列表。)
此外,作为风格问题,在锁上使用 apply()
看起来有点别扭。 (它的通常用途是自定义 newly-created 对象。)我认为 let()
在那里会更惯用;您只需要将 this
更改为 it
即可。当然,也可以使用 old-fashioned 临时变量。
这可能不是 LockServer class 的问题,而是正在使用它的问题:
线程 1:
acquire("file1")
acquire("file2")
release("file2")
release("file1")
线程 2:
acquire("file2")
acquire("file1")
release("file1")
release("file2")
而恰好执行顺序如下:
thread1.acquire("file1")
thread2.acquire("file2")
thread1.acquire("file2") //locked by thread2, waiting
thread2.acquire("file1") //locked by thread1... BOOM, deadlock!
更新:
考虑对现有锁使用 tryLock()
(可能有一些超时)而不是简单的 lock()
:
fun tryAcquire(lockId: String?, timeout: Long, unit: TimeUnit): Boolean {
if (lockId != null) {
var success = false
lockedList.compute(lockId) { id, value ->
if (value == null) {
println("Locking file $id")
val newLock = CustomLock(id)
newLock.writeLock().lock()
success = true
println("Locked file $id")
return@compute newLock
}
println("Locking file $id Existing lock")
val lock = value.writeLock()
if (lock.tryLock() || lock.tryLock(timeout, unit)) {
success = true
println("Locked file $id")
}
return@compute value
}
return success
} else {
throw InvalidParameterException("ERROR: lockId or ownerId is null!")
}
}