【问题标题】:Locking database edit by key name通过键名锁定数据库编辑
【发布时间】:2010-05-16 17:35:24
【问题描述】:

我需要防止同时编辑数据库字段。用户正在对结构化数据字段执行推送操作,因此我想对操作进行排序,而不是简单地忽略一次编辑并进行第二次编辑。

基本上是我想做的

synchronized(key name)
{
  push value onto the database field
}

并设置同步项,以便一次只对“键名”进行一项操作。 (注意:我在简化,并不总是简单的推送)。

执行此操作的粗略方法是全局同步,但这会成为整个应用程序的瓶颈。我需要做的就是使用相同的密钥对两个同时写入进行排序,这种情况很少见但很烦人。

这是一个基于 Web 的 Java 应用程序,使用 Spring(并使用 JPA/MySQL)编写。该操作由用户 Web 服务调用触发。 (根本原因是当用户同时发送两个具有相同密钥的 http 请求时)。

我浏览了 Doug Lea/Josh Bloch/et al Concurrency in Action,但没有看到明显的解决方案。不过,这似乎很简单,我觉得必须有一种优雅的方式来做到这一点。

【问题讨论】:

    标签: java spring jpa concurrency


    【解决方案1】:

    可能有一种简单的方法可以让您的数据库为您处理这些问题。诚然,我在数据库方面的知识很薄弱。取而代之的是,这里有一种方法,它涉及为每个键名创建一个单独的锁。有一个单独的存储库管理单个锁的创建/销毁,需要一个针对整个应用程序的锁,但它仅在找到、创建或销毁单个键名锁时才持有该锁.为实际数据库操作持有的锁是该操作中使用的键名专有的。

    KeyLock 类用于防止对单个键名同时进行数据库操作。

    package KeyLocks;
    
    import java.util.concurrent.locks.Lock;
    
    public class KeyLock
    {
        private final KeyLockManager keyLockManager;
        private final String keyName;
        private final Lock lock;
    
        KeyLock(KeyLockManager keyLockManager, String keyName, Lock lock)
        {
            this.keyLockManager = keyLockManager;
            this.keyName = keyName;
            this.lock = lock;
        }
    
        @Override
        protected void finalize()
        {
            release();
        }
    
        public void release()
        {
            keyLockManager.releaseLock(keyName);
        }
    
        public void lock()
        {
            lock.lock();
        }
    
        public void unlock()
        {
            lock.unlock();
        }
    }
    

    KeyLockManager 类是负责密钥锁生命周期的存储库。

    package KeyLocks;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class KeyLockManager
    {
        private class LockEntry
        {
            int acquisitionCount = 0;
            final Lock lock = new ReentrantLock();
        }
    
        private final Map<String, LockEntry> locks = new HashMap<String, LockEntry>();
        private final Object mutex = new Object();
    
        public KeyLock getLock(String keyName)
        {
            synchronized (mutex)
            {
                LockEntry lockEntry = locks.get(keyName);
                if (lockEntry == null)
                {
                    lockEntry = new LockEntry();
                    locks.put(keyName, lockEntry);
                }
                lockEntry.acquisitionCount++;
                return new KeyLock(this, keyName, lockEntry.lock);
            }
        }
    
        void releaseLock(String keyName)
        {
            synchronized (mutex)
            {
                LockEntry lockEntry = locks.get(keyName);
                lockEntry.acquisitionCount--;
                if (lockEntry.acquisitionCount == 0)
                {
                    locks.remove(keyName);
                }
            }
        }
    }
    

    以下是如何使用钥匙锁的示例。

    package test;
    
    import KeyLocks.KeyLock;
    import KeyLocks.KeyLockManager;
    
    public class Main
    {
        private static final String KEY_NAME = "TEST_KEY";
    
        public static void main(String[] args)
        {
            final KeyLockManager keyLockManager = new KeyLockManager();
            KeyLock keyLock = null;
            try
            {
                keyLock = keyLockManager.getLock(KEY_NAME);
                keyLock.lock();
                try
                {
                    // Do database operation on the data with the specified key name
                }
                finally
                {
                    keyLock.unlock();
                }
            }
            finally
            {
                if (keyLock != null)
                {
                    keyLock.release();
                }
            }
        }
    }
    

    【讨论】:

    • 不错。易于实现,更重要的是,易于在代码中调用。也不会锁定超出需要的范围。正是我想要的。
    • @Will。请注意,此解决方案不保证任何订购。如果同一个 key 涉及两个请求,则无法保证哪个请求将首先处理。从您提供的内容中不清楚您是否有订购要求。我只是想让你知道这个解决方案不会保持秩序。
    • 这是有道理的,因为这些基本上是同时请求。
    • 我实现了这个,它几乎可以工作,但是有一个错误。 KeyLock 上的 finalize 方法导致锁被释放两次。在多线程情况下,当合法线程释放锁时,这会导致 NPE in release。不幸的是,您假设调用者足够好,可以正确释放锁。我假设如果我感觉强烈,我可以使用中断锁并定期清理它,但这个解决方案非常好。 (删除终结器后)。
    • 解决方案是添加某种类型的标志,KeyLock 对象可以维护该标志来指示锁是否已被释放。然后,释放方法应该在实际执行释放之前检查锁是否已经被释放。执行发布后,应修改标志以指示发布已完成。
    【解决方案2】:

    即使您锁定了密钥,也无法确保下次它是相同的(不仅是相等的)密钥。您需要的是数据库完成的select for update 之类的东西。如果这不可能,您需要使用一组同步的锁定密钥作为您的存储库/dao 的成员以编程方式自己锁定密钥。

    【讨论】:

    • 是的,完全正确。我基本上是在寻求程序化解决方案的帮助。
    • 实际上,Select for Update 看起来与一般情况相关。我更喜欢编程解决方案而不是专门的 MySQL 命令,因为它让我可以使用我现有的 Spring/JPA 抽象。
    【解决方案3】:

    【讨论】:

    • 此处不适用。我需要两个编辑按顺序进行。每个都获取现有数据,将一个新字段插入到结构化对象中,然后存储它。如果它们是不知道彼此更改的独立事务,则这是行不通的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多