【问题标题】:Race condition in Resource ID based locking manager基于资源 ID 的锁定管理器中的竞争条件
【发布时间】:2020-10-17 01:34:14
【问题描述】:

我正在尝试编写一个可以从多个线程调用的锁定管理器。此管理器根据各种资源 ID 处理锁定。这些可能会有很大差异,因此为每个锁保留内存可能会导致大量内存使用。这就是为什么一个锁不再使用后(使用它的线程数达到0),它会从内存中删除。

它可以根据请求的资源 ID 独占锁定线程(如果两个线程锁定相同的 ID,一个将等待另一个解锁它),或者使用 ReentrantReadWriteLock 完全排除所有其他线程。

我遇到了一种竞争情况,当最后一个持有它的线程解锁时,它从内存中删除了一个锁,但其他线程仍在尝试解锁它?这导致了我无法解释的 NPE。

我尝试使用 AtomicInteger 代替当前的 volatile 变量,认为它可能与此有关,但结果相似。

这是有问题的类:

/**
 * This class provides locks for reading and writing, and bulk operations lock on the entire class.
 * 
 * If a bulk operation is not in progress, class based locking is transparent.
 * @author KiralyCraft
 *
 */
public class ReadWriteHighLevelLocking
{
    private class Semaphore
    {
        private ReentrantLock lock;
        private volatile int acquiredLocks;
        public Semaphore()
        {
            this.acquiredLocks = 0;
            this.lock = new ReentrantLock();
        }
        
        public synchronized int incrementAndGet()
        {
            return ++acquiredLocks;
        }
        
        public synchronized int decrementAndGet()
        {
            return --acquiredLocks;
        }
    }
    
    private ReentrantReadWriteLock classBasedLock;
    private volatile HashMap<String, Semaphore> stateChangeLocks;
    
    public ReadWriteHighLevelLocking()
    {
        this.stateChangeLocks = new HashMap<String,Semaphore>();
        this.classBasedLock = new ReentrantReadWriteLock();
    }
    
    /**
     * Acquires a lock for the specified resource ID.
     * 
     * May block if another thread is currently holding a bulk lock.
     * @param resourceID
     */
    public void acquireLock(String resourceID)
    {
        classBasedLock.readLock().lock(); //Using it reversed. There can be any number of operations (using the read lock), but only one bulk operation (sacrifice)
        Semaphore stateChangeLock;
        synchronized(stateChangeLocks)
        {
            if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
            {
                stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
            }
        }
        stateChangeLock.lock.lock();
        stateChangeLock.incrementAndGet();
    }
    public void releaseLock(String resourceID)
    {
        Semaphore stateChangeLock;
        synchronized(stateChangeLocks)
        {
            stateChangeLock = stateChangeLocks.get(resourceID);
            if (stateChangeLock.decrementAndGet() == 0)  //<----------------- HERE IS THE NPE
            {
                stateChangeLocks.remove(resourceID);
            }
        }
        stateChangeLock.lock.unlock();
        classBasedLock.readLock().unlock();
    }
    
    /**
     * When a bulk lock is acquired, all other operations are delayed until this one is released.
     */
    public void acquireBulkLock()
    {
        classBasedLock.writeLock().lock();  //Using it reversed. There can be any number of writers (using the read lock), but only one reader (sacrifice)
    }
    
    public void releaseBulkLock()
    {
        classBasedLock.writeLock().unlock(); 
    }
}

示例调用者类:

public abstract class AbstractDatabaseLockingController
{
    ...
    private ReadWriteHighLevelLocking highLevelLock;
    
    public AbstractDatabaseLockingController(DatabaseInterface db)
    {
        this.db = db;
        this.highLevelLock = new ReadWriteHighLevelLocking();
    }   
    ...
    public <T extends DatabaseIdentifiable> boolean executeSynchronizedUpdate(T theEntity,AbstractSynchronousOperation<T> aso)
    {
        boolean toReturn;
        String lockID = theEntity.getId()+theEntity.getClass().getSimpleName();
        highLevelLock.acquireLock(lockID);
        toReturn = aso.execute(theEntity,db);
        highLevelLock.releaseLock(lockID);
        return toReturn;        
    }
    ...
    public <T extends DatabaseIdentifiable> List<T> executeSynchronizedGetAllWhereFetch(Class<T> objectType, DatabaseQuerySupplier<T> dqs)
    {
        List<T> toReturn;
        highLevelLock.acquireBulkLock();
        toReturn = db.getAllWhere(objectType, dqs);
        highLevelLock.releaseBulkLock();
        return toReturn;
    }
}

注意:所有使用这种锁定管理器的地方都遵循示例类中的获取/释放模式。它基本上是唯一使用它的地方。其他线程可能会通过示例类的子类间接调用上述方法

【问题讨论】:

  • 从高层次上看,stateChangeLock = stateChangeLocks.get(resourceID);从未初始化,您能否提供一个获取锁的示例 Thread 类??
  • 是的,我添加了调用类的示例。锁总是先获取,然后释放,我已经仔细检查过
  • 好吧,您可以尝试将 stateChangeLocks 设为 final,因为锁表达式是对非 final 字段的引用的同步语句,这样的语句不太可能具有有用的语义,因为不同的线程可能是即使对同一个对象进行操作,也会锁定不同的对象。

标签: java multithreading locking


【解决方案1】:

我似乎通过更新以下代码解决了这个问题:

    synchronized(stateChangeLocks)
    {
        if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
        {
            stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
        }
    }
    stateChangeLock.lock.lock();
    stateChangeLock.incrementAndGet();

    synchronized(stateChangeLocks)
    {
        if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
        {
            stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
        }
        stateChangeLock.incrementAndGet();
    }
    stateChangeLock.lock.lock();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-14
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    相关资源
    最近更新 更多